<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Percona Community Blog - learn about MySQL, MariaDB, PostgreSQL, and MongoDB</title><link>https://percona.community/blog/</link><description>Percona Community Blog is a place where you can learn and get best from the community knowledge about open source databases (MySQL, PostgreSQL, MariaDB, and MongoDB) and various tools. Check out some of the great free content and contribute and share your experience with other community members.</description><atom:link href="https://percona.community/blog/index.xml" rel="self" type="application/rss+xml"/><generator>Hugo</generator><language>en-us</language><copyright>© Percona Community. MySQL, InnoDB, MariaDB and MongoDB are trademarks of their respective owners.</copyright><lastBuildDate>Thu, 10 Sep 2026 10:03:31 PDT</lastBuildDate><item><title>The Perfect Serve: What Tennis Taught Me About Reading PMM Like a Coach Instead of Guessing</title><link>https://percona.community/blog/2026/09/10/the-perfect-serve/</link><guid>https://percona.community/blog/2026/09/10/the-perfect-serve/</guid><pubDate>Thu, 10 Sep 2026 12:00:00 UTC</pubDate><description>When I started learning tennis as an adult, I realized that what made tennis difficult was not learning the tennis strokes, instead it was learning to realize the value of the feedback loop: racket vibrations, tennis ball sounds, and the way my balance shifted before I even realized it. None of this information is obvious on the court. However, without it I would continue to make the same significant error, and be frustrated that “trying harder” doesn’t seem to aid.</description><content:encoded>&lt;p>When I started learning tennis as an adult, I realized that what made tennis difficult was not learning the tennis strokes, instead it was learning to realize the value of the feedback loop: racket vibrations, tennis ball sounds, and the way my balance shifted before I even realized it. None of this information is obvious on the court. However, without it I would continue to make the same significant error, and be frustrated that “trying harder” doesn’t seem to aid.&lt;/p>
&lt;p>A similar realization came to me a few months ago, although this time was pretty different. Think of Percona Monitoring and Management (PMM) as the tennis racket and Percona Server for MySQL 8.0 as the tennis court. The measured result was a 95.0% reduction in average query time, from 54.17 ms to 2.71 ms. The only thing that changed was my perspective, not the setup.&lt;/p>
&lt;h2 id="the-setup">The Setup&lt;/h2>
&lt;p>Very basic setup. One Percona Server for MySQL 8.0 primary, two EC2 read replicas, and PMM 2.x running in a Docker container, with QAN through performance_schema. Nothing special. The symptom was also standard: an internal analytics service joined three tables (orders, order_items, and users) and polled the primary every few minutes for recent order activity. None of the tables were big.&lt;/p>
&lt;p>The screenshots and measurements in this walkthrough come from a lab environment created to reproduce the query pattern, not from a production incident.&lt;/p>
&lt;p>In the baseline window, the query ran at 0.16 QPS and averaged 54.17 ms. It also lined up closely with CPU spikes and occasional replica-lag warnings.&lt;/p>
&lt;p>My “power play” instinct kicked in, and I thought about increasing the resources for the primary, or moving the analytic workload totally over to a replica. Both ideas had potential, but at the end of the day, they would just hide the problem. None of the ideas actually would have helped me understand what the problem was.&lt;/p>
&lt;h2 id="reading-the-court-qan-sorted-by-load-not-raw-query-time">Reading the Court: QAN Sorted by Load, Not Raw Query Time&lt;/h2>
&lt;p>The first real change in my mindset came from how I was sorting Query Analytics. The usual way to do this is to look for the query with the maximum or mean query time, and sort the results that way.&lt;/p>
&lt;p>For this reason, PMM’s QAN dashboard has a Load column. Load is the average number of simultaneous executions of a query. Instead of looking at how long a query takes when executed, a better way to look at this is how much of the server’s attention is this query consuming, on average, right now. When sorting by Load, the three-table join jumped to the top. An average execution time of 54.17 ms does not look problematic until you realize the query is executed multiple times a minute, over and over again, continuously throughout the day.&lt;/p>
&lt;p>That’s the first lesson: If a query never stops running, a query that is “fast enough” can still be your biggest rival.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2506" height="918" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/baseline_qan_hu_8c02abee17bdbf99.webp 768w, https://percona.community/blog/2026/09/baseline_qan_hu_45fcddf26caca0b3.webp 480w, https://percona.community/blog/2026/09/baseline_qan_hu_ac4be93c5ac16a26.webp 1400w" src="https://percona.community/blog/2026/09/baseline_qan_hu_ac4be93c5ac16a26.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Figure 1: PMM Query Analytics showing the three-table join at the top of the workload, with an average query time of 54.17 ms before optimization.&lt;/em>&lt;/p>
&lt;h2 id="film-review-before-stepping-on-the-court-explain">Film Review Before Stepping on the Court: EXPLAIN&lt;/h2>
&lt;p>Once QAN pointed at the query, the next step was to observe EXPLAIN, as a coach would observe game film: review it before making any changes in production.&lt;/p>
&lt;p>The query, in simplified form, looked like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">total_amount&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">oi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">product_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">oi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">quantity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">oi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">price&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">order_items&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">oi&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">oi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">order_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NOW&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INTERVAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">MINUTE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DESC&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>EXPLAIN confirmed this was a perfect example for a full table scan on orders (type: ALL). MySQL had to read every row to filter for the time window, then it used a nested loop to join order_items and users, which exacerbated the original issue.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">orders:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: ALL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rows: 199875
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Extra: Using where; Using filesort
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">users:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: eq_ref
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: PRIMARY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">order_items:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: ref
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: idx_order_items_order_id&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Thanks to PMM’s rows examined to rows sent ratio, the query’s inefficiency actually showed itself where it could not in raw EXPLAIN output. The query would even return a reasonably sized and useful result. The issue was that the ratio of rows examined and rows sent to the client was totally lopsided. Each execution examined about 201,370 rows while returning 819 rows, or 245.87 rows examined for every row sent. This would not trigger any system alerts, but it showed that the database was doing SO much work for a trivial result. I’m calling this “invisible” inefficiency, and the more the query becomes used, the more detrimental it becomes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2548" height="1334" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/baseline_examined_hu_1167fcf55b3ed527.webp 768w, https://percona.community/blog/2026/09/baseline_examined_hu_ca84804998ad1ffe.webp 480w, https://percona.community/blog/2026/09/baseline_examined_hu_a5d12ec61d5a6df9.webp 1400w" src="https://percona.community/blog/2026/09/baseline_examined_hu_a5d12ec61d5a6df9.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Figure 2: Before optimization, each execution examined about 201,370 rows while returning 819 rows, or 245.87 rows examined for every row sent. PMM also recorded a full scan and no index used on every execution.&lt;/em>&lt;/p>
&lt;h2 id="the-fix-small-targeted-adjustments">The Fix: Small, Targeted Adjustments&lt;/h2>
&lt;p>This was actually the best example of tennis in the real world for me. The tendency in this sort of query optimization is to swing harder (a bigger instance, more read replicas, etc.), but this was more like lightly changing the grip by a few grams.&lt;/p>
&lt;p>Two index changes, one query change, and that was it.&lt;/p>
&lt;p>A composite index on orders that matches the access pattern:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">idx_orders_created_at_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The composite index on (created_at, id) allowed MySQL to use a range scan on the created_at predicate instead of scanning the entire orders table. In EXPLAIN, the orders access changed from type: ALL with roughly 199,875 rows estimated to type: range using idx_orders_created_at_id. The id column does not make this access covering, because the query still needs total_amount and user_id from the orders row.&lt;/p>
&lt;p>A covering index on order_items:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">idx_order_items_covering&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">order_items&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">order_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">product_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">quantity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">price&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The covering index on (order_id, product_id, quantity, price) allowed MySQL to satisfy the join and requested item columns from the index itself. EXPLAIN reported Using index.&lt;/p>
&lt;p>The last change was to split the users lookup from the main join. The first query returned user_id, and a second query used WHERE id IN (…) to fetch the email addresses from users.&lt;/p>
&lt;p>None of these improvements required any downtime, schema redesigns, or having to talk about sizing the instances. Each improvement was the kind of adjustment where it looked like, on its own, nothing particularly important was actually done.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">orders:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: range
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: idx_orders_created_at_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rows: 40
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Extra: Using index condition; Backward index scan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">users:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: eq_ref
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: PRIMARY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">order_items:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: ref
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">key: idx_order_items_covering
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Extra: Using index&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="the-results">The Results&lt;/h2>
&lt;p>The same normalized query remained at the top of the selected QAN workload after optimization, now running at 0.99 QPS and averaging 2.71 ms per execution.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2040" height="822" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/optimised_qan_hu_db97c69e95600cd5.webp 768w, https://percona.community/blog/2026/09/optimised_qan_hu_ef236d53ae68fbaf.webp 480w, https://percona.community/blog/2026/09/optimised_qan_hu_ef292a84f6950e70.webp 1400w" src="https://percona.community/blog/2026/09/optimised_qan_hu_ef292a84f6950e70.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Figure 3: The same normalized query after optimization, now averaging 2.71 ms per execution in the post-index sampling window.&lt;/em>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2034" height="620" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/optimised_metrics_hu_e5d4e4a512930806.webp 768w, https://percona.community/blog/2026/09/optimised_metrics_hu_df93540e768419d2.webp 480w, https://percona.community/blog/2026/09/optimised_metrics_hu_990c9680b2429206.webp 1400w" src="https://percona.community/blog/2026/09/optimised_metrics_hu_990c9680b2429206.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Figure 4: After the changes, average query time fell to 2.71 ms and rows examined dropped to about 1,460 per execution. The rows-examined-to-rows-sent ratio fell to 1.67, and executions used range access instead of a full scan.&lt;/em>&lt;/p>
&lt;p>Regarding the query, QAN indicated that average query time decreased from 54.17 ms to 2.71 ms, yielding a 95.0% reduction and making the query about 20 times faster. Rows examined per query fell from 201.37k to 1.46k, a 99.27% reduction, or about 138 times fewer rows examined. The ratio of rows examined to rows sent fell from 245.87 to 1.67, a 99.32% reduction. Full scan and no index used disappeared from the optimized sampling window, while Select Range reached 1.00 per query.&lt;/p>
&lt;p>Improvements were the result of a number of incremental changes rather than a single large scale change. Composite indexing of orders and covering indexing of order_items helped rationalize the query to a considerable extent. Together, the changes rationalized the query and had an impact that would otherwise have been easy to chase with additional hardware.&lt;/p>
&lt;h2 id="shifting-your-mindset">Shifting Your Mindset&lt;/h2>
&lt;p>Beyond the specific indexes, the key point of this case study is how the investigation was framed at the outset.&lt;/p>
&lt;p>QAN was sorted by Load. This let me identify the query that consumed the most attention from the primary. Unlike the query that had the most problematic single execution time.&lt;/p>
&lt;p>Taking time to analyze EXPLAIN and the rows-examined-to-rows-sent ratio before implementing the fix let me focus on the cause of the problem. A full scan on orders. Instead of a symptom, which was a CPU spike with a concurrent lag on the replica.&lt;/p>
&lt;p>By implementing two specific indexes and a small query rewrite, I ended up with measurable, attributable, and repeatable results. And if something was negatively impacted after the implementation of the fix, I would be able to identify which specific change caused the problem.&lt;/p>
&lt;p>The engineer that looks as if they are moving the least in a tennis match, because they have already anticipated the next three shots, is the one doing the most invisible work, and that is exactly what this kind of production database work looks like from the outside.&lt;/p>
&lt;p>PMM didn’t perform the optimization, but it made the invisible work just visible enough to act on the changes, and the rest was patience, and a willingness to implement incremental changes rather than larger changes.&lt;/p>
&lt;h2 id="about-the-author">About the author&lt;/h2>
&lt;p>Shivank Pandey is one of two engineers managing PMM at his company. He works with Percona Server for MySQL 8.0 in a primary-replica setup on EC2 and writes about database observability and query optimization.&lt;/p>
&lt;p>&lt;em>This post is part of the &lt;a href="https://percona.community/blog/write-for-percona-community/">Percona Community Writers Program&lt;/a>.&lt;/em>&lt;/p></content:encoded><author>Shivank Pandey</author><category>PMM</category><category>Query Optimization</category><category>Community</category><media:thumbnail url="https://percona.community/blog/2026/09/baseline_qan_hu_d72ca55a88da5bb1.jpg"/><media:content url="https://percona.community/blog/2026/09/baseline_qan_hu_e71d5e2407d96f72.jpg" medium="image"/></item><item><title>A Fail-Fast PostgreSQL Migration Preflight for CI</title><link>https://percona.community/blog/2026/09/08/postgresql-migration-preflight-ci/</link><guid>https://percona.community/blog/2026/09/08/postgresql-migration-preflight-ci/</guid><pubDate>Tue, 08 Sep 2026 11:00:00 UTC</pubDate><description>A database migration can be syntactically correct and still be a poor release candidate. A regular index build can block writes, an ALTER TABLE may wait behind a conflicting lock or hold a strong lock longer than expected, and a large data modification can turn a routine deployment into an incident.</description><content:encoded>&lt;p>A database migration can be syntactically correct and still be a poor release candidate. A regular index build can block writes, an &lt;code>ALTER TABLE&lt;/code> may wait behind a conflicting lock or hold a strong lock longer than expected, and a large data modification can turn a routine deployment into an incident.&lt;/p>
&lt;p>Static analysis cannot determine whether a migration is safe in production. It does not know table size, traffic, open transactions, data distribution, or how the migration runner executes the file. But it can catch a smaller class of mistakes where the SQL itself provides strong evidence of operational risk.&lt;/p>
&lt;p>I built &lt;a href="https://github.com/yZangEren/postgres-migration-preflight" target="_blank" rel="noopener noreferrer">postgres-migration-preflight&lt;/a> as an experiment in that narrower problem. It parses PostgreSQL SQL into an abstract syntax tree (AST), evaluates statements in source order, and exits non-zero when configured blocking rules fire. The examples and documentation references below use PostgreSQL 18.&lt;/p>
&lt;p>The goal is not to certify a migration as production-safe. It is to make obvious hazards difficult to merge unnoticed.&lt;/p>
&lt;h2 id="focus-on-risks-visible-from-sql">Focus on risks visible from SQL&lt;/h2>
&lt;p>The first version deliberately limits itself to rules that can be explained from the migration text:&lt;/p>
&lt;ul>
&lt;li>regular &lt;code>CREATE INDEX&lt;/code> and &lt;code>DROP INDEX&lt;/code> operations;&lt;/li>
&lt;li>selected lock-sensitive &lt;code>ALTER TABLE&lt;/code> operations;&lt;/li>
&lt;li>destructive DDL such as &lt;code>DROP TABLE&lt;/code> and &lt;code>TRUNCATE&lt;/code>;&lt;/li>
&lt;li>&lt;code>UPDATE&lt;/code> or &lt;code>DELETE&lt;/code> statements without a &lt;code>WHERE&lt;/code> clause;&lt;/li>
&lt;li>DDL without a preceding non-zero session-level &lt;code>lock_timeout&lt;/code>;&lt;/li>
&lt;li>SQL that the parser cannot understand.&lt;/li>
&lt;/ul>
&lt;p>These rules are heuristics, not proofs. A normal &lt;code>CREATE INDEX&lt;/code> allows reads but blocks inserts, updates, and deletes while the index is built. &lt;code>CREATE INDEX CONCURRENTLY&lt;/code> avoids blocking those writes, but PostgreSQL performs two table scans, waits for relevant transactions, does more total work, and can leave an invalid index after certain failures. It also cannot run inside a transaction block. A migration framework may create such a transaction even when the SQL file contains no &lt;code>BEGIN&lt;/code>, so the finding asks the reviewer to check both the index operation and the runner.&lt;/p>
&lt;p>A normal &lt;code>DROP INDEX&lt;/code> acquires an &lt;code>ACCESS EXCLUSIVE&lt;/code> lock on the table. Its concurrent form reduces that interference but cannot run in a transaction block, cannot use &lt;code>CASCADE&lt;/code>, and cannot remove an index that supports a &lt;code>UNIQUE&lt;/code> or &lt;code>PRIMARY KEY&lt;/code> constraint.&lt;/p>
&lt;p>&lt;code>ALTER TABLE&lt;/code> is a family of operations rather than one risk category. A column type change normally rewrites the table, but PostgreSQL can avoid the rewrite for some binary-compatible conversions. &lt;code>SET NOT NULL&lt;/code> ordinarily scans existing rows, but can skip the scan when a valid &lt;code>CHECK&lt;/code> constraint proves that no &lt;code>NULL&lt;/code> can exist. A foreign key added as &lt;code>NOT VALID&lt;/code> can enforce new writes before a separate &lt;code>VALIDATE CONSTRAINT&lt;/code> checks existing rows with a less disruptive lock.&lt;/p>
&lt;p>The current lab still blocks every column type change conservatively because it has no catalog context. That severity is a policy choice for review, not proof that a particular conversion will rewrite a table.&lt;/p>
&lt;h2 id="preserve-order-and-fail-closed">Preserve order and fail closed&lt;/h2>
&lt;p>Some protections matter only when they appear before the statement they are meant to protect:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLUMN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TYPE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">320&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">lock_timeout&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'2s'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The timeout does not protect the preceding &lt;code>ALTER TABLE&lt;/code>. The analyzer walks statements in source order and tracks the session-level &lt;code>lock_timeout&lt;/code>. A non-zero value counts only for subsequent DDL, and setting it back to &lt;code>0&lt;/code> disables the protection again.&lt;/p>
&lt;p>This is a review signal, not a claim that a timeout makes DDL safe. &lt;code>lock_timeout&lt;/code> limits only time spent waiting to acquire a lock. After the lock is acquired, an operation can continue much longer. An equal or shorter &lt;code>statement_timeout&lt;/code> can also fire first.&lt;/p>
&lt;p>The analyzer uses &lt;code>pgsql-ast-parser&lt;/code> instead of regular expressions so supported rules operate on statement structure. It can distinguish an &lt;code>UPDATE&lt;/code> with a predicate, a concurrent index declaration, and individual &lt;code>ALTER TABLE&lt;/code> actions. An AST still does not provide catalog state or workload context. If the parser cannot understand valid PostgreSQL syntax, the tool emits &lt;code>SQL_PARSE_ERROR&lt;/code> at blocker severity and exits non-zero. Parser failures are coverage gaps requiring review, not evidence that PostgreSQL would reject the migration.&lt;/p>
&lt;h2 id="compare-passing-and-blocked-migrations">Compare passing and blocked migrations&lt;/h2>
&lt;p>A deliberately small passing example is:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">lock_timeout&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'2s'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">statement_timeout&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'15min'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CONCURRENTLY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">idx_users_last_seen_at&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">last_seen_at&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">archived&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The checker parses four statements and reports no findings. That result does not make the migration automatically safe: the caller still has to ensure the concurrent index statement is not executed inside a transaction block.&lt;/p>
&lt;p>The blocked example contains stronger static signals:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">idx_users_email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLUMN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TYPE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">320&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">DELETE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">audit_events&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The checker reports five findings: two critical, one high, and two medium. The critical findings cover the column type change and unbounded delete. The regular index build is high severity, while the medium findings identify DDL without a preceding non-zero &lt;code>lock_timeout&lt;/code>. The process exits with code &lt;code>1&lt;/code>, so CI can stop before deployment.&lt;/p>
&lt;p>The examples test rule behavior, not the hardest PostgreSQL migration problems. A &lt;code>WHERE&lt;/code> clause can still affect most rows of a large table, and a binary-compatible type change can be much cheaper than another statement with similar syntax. Production-scale changes still require workload-aware review.&lt;/p>
&lt;h2 id="give-ci-a-simple-contract">Give CI a simple contract&lt;/h2>
&lt;p>The CLI exits with &lt;code>0&lt;/code> when no blocking finding exists, &lt;code>1&lt;/code> when the migration is blocked, and &lt;code>2&lt;/code> for invalid command usage. A minimal workflow is:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Install migration preflight&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">npm ci&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Test the analyzer&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">npm test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Check the pending migration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">node src/check-migration.mjs path/to/migration.sql&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>JSON output can feed CI annotations or review tooling. The current test suite covers the passing case, the risky case, ordered timeout handling, and parser failure. I would introduce a gate gradually: start with rules supported by strong SQL evidence, measure false positives, and only then make broader organization-specific policies blocking.&lt;/p>
&lt;p>The checker reports findings rather than rewriting migrations. Adding &lt;code>CONCURRENTLY&lt;/code> can change transaction requirements and recovery steps, while splitting constraint creation and validation changes deployment sequencing. Those decisions still belong to the developer and reviewer.&lt;/p>
&lt;h2 id="keep-the-boundary-explicit">Keep the boundary explicit&lt;/h2>
&lt;p>SQL text cannot reveal relation size, data distribution, long-running transactions, current lock holders, replica lag, WAL capacity, application traffic, or a migration framework’s transaction policy. PostgreSQL versions also differ in DDL behavior and available optimizations.&lt;/p>
&lt;p>A passing preflight should therefore be followed by production-sized rehearsal and runtime verification for consequential migrations. That may include checking expected scans or rewrites, monitoring locks and replica lag, planning rollback procedures, and verifying constraints and indexes afterward. Following a failed concurrent index build, catalog state such as &lt;code>pg_index.indisvalid&lt;/code> also needs inspection.&lt;/p>
&lt;p>The value of a static gate is not that it understands production. Its value is that some mistakes do not require production knowledge to recognize. Catching those mistakes in CI leaves reviewers more time for the harder questions that static analysis cannot answer.&lt;/p>
&lt;p>That is the boundary I want &lt;code>postgres-migration-preflight&lt;/code> to maintain: a small, explainable first line of defense for PostgreSQL migrations, not a production-readiness oracle.&lt;/p>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/indexes-intro.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: Introduction to indexes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/sql-createindex.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: CREATE INDEX&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/sql-dropindex.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: DROP INDEX&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/sql-altertable.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: ALTER TABLE&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/runtime-config-client.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: Client connection defaults, including &lt;code>lock_timeout&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/catalog-pg-index.html" target="_blank" rel="noopener noreferrer">PostgreSQL 18: &lt;code>pg_index&lt;/code> catalog&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;em>This post is part of the &lt;a href="https://percona.community/blog/write-for-percona-community/">Percona Community Writers Program&lt;/a>.&lt;/em>&lt;/p></content:encoded><author>Yi</author><category>PostgreSQL</category><category>Migrations</category><category>CI</category><category>Testing</category><category>QA</category><category>DevOps</category><media:thumbnail url="https://percona.community/blog/2026/09/postgresql-migration-preflight-ci-cover_hu_50dada8006040b9c.jpg"/><media:content url="https://percona.community/blog/2026/09/postgresql-migration-preflight-ci-cover_hu_c7723042a9495c1f.jpg" medium="image"/></item><item><title>ProxySQL HA with BGP ECMP Anycast</title><link>https://percona.community/blog/2026/09/07/proxysql-ha-with-bgp-ecmp-anycast/</link><guid>https://percona.community/blog/2026/09/07/proxysql-ha-with-bgp-ecmp-anycast/</guid><pubDate>Mon, 07 Sep 2026 11:00:00 UTC</pubDate><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?</description><content:encoded>&lt;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?&lt;/p>
&lt;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…&lt;/p>
&lt;p>At some point, you may land on the common HA strategy, keepalived, but BGP ECMP is a powerful alternative worth considering.&lt;/p>
&lt;h2 id="what-is-bgp-what-is-ecmp">What is BGP, what is ECMP?&lt;/h2>
&lt;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.&lt;/p>
&lt;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.&lt;/p>
&lt;p>For a more in depth guide about BGP please refer to &lt;a href="https://www.ciscopress.com/articles/article.asp?p=2738462&amp;seqNum=2" target="_blank" rel="noopener noreferrer">Cisco press&lt;/a>.&lt;/p>
&lt;h2 id="how-ecmp-based-on-bgp-works">How ECMP based on BGP works&lt;/h2>
&lt;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.&lt;/p>
&lt;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:&lt;/p>
&lt;p>&lt;code>{ source IP address | destination IP address | protocol | source port | destination port }&lt;/code>&lt;/p>
&lt;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.&lt;/p>
&lt;p>You can visualise the setup like this:&lt;/p>
&lt;p>
&lt;figure>&lt;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" />&lt;/figure>&lt;/p>
&lt;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.&lt;/p>
&lt;h2 id="alternative-strategies-for-using-bgp-with-databases">Alternative strategies for using BGP with databases&lt;/h2>
&lt;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).&lt;/p>
&lt;h3 id="routing-towards-the-database-loadbalancer">Routing towards the database loadbalancer&lt;/h3>
&lt;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.&lt;/p>
&lt;h3 id="read-replica-routing">Read replica routing&lt;/h3>
&lt;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.&lt;/p>
&lt;h3 id="bgp-local-preference">BGP Local Preference&lt;/h3>
&lt;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.&lt;/p>
&lt;p>
&lt;figure>&lt;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" />&lt;/figure>&lt;/p>
&lt;h2 id="advantages-of-bgp">Advantages of BGP&lt;/h2>
&lt;ul>
&lt;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.&lt;/li>
&lt;li>BGP ECMP supports active/active, unlike keepalived which only supports active/passive architectures.&lt;/li>
&lt;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.&lt;/li>
&lt;li>You can horizontally scale the nodes with BGP.&lt;/li>
&lt;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.”&lt;/li>
&lt;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.&lt;/li>
&lt;/ul>
&lt;h2 id="caveats-with-bgp">Caveats with BGP&lt;/h2>
&lt;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.&lt;/p>
&lt;p>In the next post, we will explain the technical details of setting up BGP ECMP for our ProxySQL cluster using OPNsense as Router.&lt;/p>
&lt;p>&lt;em>This post is part of the &lt;a href="https://percona.community/blog/write-for-percona-community/">Percona Community Writers Program&lt;/a>.&lt;/em>&lt;/p></content:encoded><author>Isobel Smith</author><author>Marno Krahmer</author><category>MySQL</category><category>ProxySQL</category><category>Opensource</category><category>BGP</category><category>Percona Server</category><category>DevOps</category><media:thumbnail url="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-cover_hu_5e1233ab150bfe64.jpeg"/><media:content url="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-cover_hu_f07bb5541e642688.jpeg" medium="image"/></item><item><title>Building Useful Community Software with MongoDB Using AI Agents</title><link>https://percona.community/blog/2026/08/31/community-leaderboard/</link><guid>https://percona.community/blog/2026/08/31/community-leaderboard/</guid><pubDate>Mon, 31 Aug 2026 11:00:00 UTC</pubDate><description>When activity spreads across GitHub, the forum, and content, real contribution is easy to lose in the noise. A community program only works when people trust the numbers behind the thank-you, so the leaderboard has to be accurate and explainable. For our Community Leaderboard we built scheduled indexers for those sources, Percona Server for MongoDB (PSMDB) as a flexible store for messy multi-source data, private dashboards to verify staff vs community and identity maps, and a daily JSON feed that powers a static Hugo widget. AI agents took care of much of the boilerplate: API clients, first-pass field mappings, early widget prototypes. That let us spend the week on rules, trust, and the public experience. The same pattern should transfer if you run a community or like database-shaped side projects.</description><content:encoded>&lt;p>When activity spreads across GitHub, the forum, and content, real contribution is easy to lose in the noise. A community program only works when people trust the numbers behind the thank-you, so the leaderboard has to be accurate and explainable. For our &lt;a href="https://percona.community/ascent/leaderboard/" target="_blank" rel="noopener noreferrer">Community Leaderboard&lt;/a> we built scheduled indexers for those sources, &lt;a href="https://docs.percona.com/percona-server-for-mongodb/8.0/index.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> (PSMDB) as a flexible store for messy multi-source data, private dashboards to verify staff vs community and identity maps, and a daily JSON feed that powers a static Hugo widget. &lt;strong>AI agents&lt;/strong> took care of much of the boilerplate: API clients, first-pass field mappings, early widget prototypes. That let us spend the week on rules, trust, and the public experience. The same pattern should transfer if you run a community or like database-shaped side projects.&lt;/p>
&lt;p>In July our community lead &lt;strong>Laura Czajkowski&lt;/strong> published &lt;a href="https://percona.community/blog/2026/07/16/introducing-mountaineers/">Introducing Mountaineers: A Way to Say Thank You&lt;/a>. That program is why we built the board. Laura wanted contribution to stop disappearing into noise: the bug filed late at night, the forum thread that sat unanswered, the PR, the hour telling engineering what’s broken. Mountaineers tracks that energy and gives something back: recognition, access, swag. Points, Basecamp, and the reward ladder are in her post and on the &lt;a href="https://percona.community/ascent/mountaineers/" target="_blank" rel="noopener noreferrer">Mountaineers&lt;/a> page. The leaderboard is the public face; my job was to make the numbers trustworthy enough for that program to work.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2816" height="2816" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/leaderboard-widget_hu_1297f07313548081.webp 768w, https://percona.community/blog/2026/08/leaderboard-widget_hu_2166f90dca76d52a.webp 480w, https://percona.community/blog/2026/08/leaderboard-widget_hu_4400601fb99204a.webp 1400w" src="https://percona.community/blog/2026/08/leaderboard-widget_hu_4400601fb99204a.webp" alt="Community Leaderboard widget for 2026, period picker open" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="why-the-board-exists">Why the board exists&lt;/h2>
&lt;p>Laura’s “what counts” list is what the board has to measure. It isn’t only code. GitHub issues, PRs, and merges count. Forum topics, replies, and accepted solutions count. Blog posts and tutorials count, including work through the &lt;a href="https://percona.community/blog/2026/05/22/write-for-percona-community/">Community Writers Program&lt;/a>. Direct feedback to engineering counts too. Show up in more than one place in the same month and the climb goes faster. The public &lt;a href="https://percona.community/ascent/leaderboard/" target="_blank" rel="noopener noreferrer">leaderboard&lt;/a> is how that becomes visible outside the team.&lt;/p>
&lt;p>A board only helps if the community trusts it. That’s where the engineering problem starts. The store holds &lt;strong>600+ Percona GitHub repositories&lt;/strong>; in &lt;strong>2026&lt;/strong> staff alone were active in &lt;strong>100+&lt;/strong> of them (&lt;strong>~150&lt;/strong> employee contributors). Staff and community work in the same channels - GitHub and the forum - so without filters you cannot tell whose work you’re looking at. Separating community PRs from staff PRs, and spotting forum questions from community members that still have no answer, isn’t something you do by scrolling notifications. You need a place where that signal is collected, filtered, and easy to inspect.&lt;/p>
&lt;p>That’s what the internal dashboards are for. They aren’t the public site. They’re how the community team finds the work worth recognizing and the threads that still need a human.&lt;/p>
&lt;p>In &lt;strong>2026&lt;/strong> so far community activity is already substantial. On GitHub: &lt;strong>~750 pull requests and issues&lt;/strong> from &lt;strong>270+ contributors&lt;/strong>, including &lt;strong>~80 merged PRs&lt;/strong>. On the forum: &lt;strong>~290 active community users&lt;/strong>, &lt;strong>~400 active topics&lt;/strong>, and &lt;strong>~900 community posts&lt;/strong>. The public board for that year already has &lt;strong>498&lt;/strong> people on it (&lt;strong>257&lt;/strong> GitHub, &lt;strong>240&lt;/strong> forum, &lt;strong>5&lt;/strong> content). Staff also post heavily in the same places, which is why the dashboards filter staff vs community before anything is scored for Mountaineers. Without that step, employee noise would bury the people the program exists to thank.&lt;/p>
&lt;p>To turn that activity into a comparable ranking, we use a simple, fixed set of weights:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>GitHub&lt;/th>
&lt;th>Content&lt;/th>
&lt;th>Forum&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Issue submitted · &lt;strong>10&lt;/strong>&lt;/td>
&lt;td>Community blog post · &lt;strong>100&lt;/strong>&lt;/td>
&lt;td>Topic created · &lt;strong>5&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PR created · &lt;strong>25&lt;/strong>&lt;/td>
&lt;td>YouTube video · &lt;strong>75&lt;/strong>&lt;/td>
&lt;td>Reply · &lt;strong>2&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PR merged · &lt;strong>75&lt;/strong>&lt;/td>
&lt;td>External article · &lt;strong>50&lt;/strong>&lt;/td>
&lt;td>Solution provided · &lt;strong>50&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Content is still thin on the board. We only recently launched the &lt;a href="https://percona.community/blog/2026/05/22/write-for-percona-community/">Community Writers Program&lt;/a>, so most climbing today is GitHub and forum. That will change as more posts land.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2502" height="1578" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/leaderboard-dashboard-leaderboard_hu_6d5de60d8bdaa6aa.webp 768w, https://percona.community/blog/2026/08/leaderboard-dashboard-leaderboard_hu_6126b7f8535b572.webp 480w, https://percona.community/blog/2026/08/leaderboard-dashboard-leaderboard_hu_5a56a6b201043b66.webp 1400w" src="https://percona.community/blog/2026/08/leaderboard-dashboard-leaderboard_hu_5a56a6b201043b66.webp" alt="Internal leaderboard: Show Community, Global tab, 498 contributors for 2026" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Those numbers and weights are the &lt;em>why&lt;/em>. The rest of the post is the &lt;em>how&lt;/em>: private dashboards, a public JSON feed, PSMDB, and a recipe you can reuse.&lt;/p>
&lt;h2 id="private-dashboards-public-site">Private dashboards, public site&lt;/h2>
&lt;p>The architecture is a bit unusual on purpose. The dashboards live in our private corporate cloud. They talk to APIs, write to PSMDB, and show rich views for staff. The &lt;a href="https://percona.community/" target="_blank" rel="noopener noreferrer">community website&lt;/a> is a static &lt;strong>Hugo&lt;/strong> site on &lt;strong>GitHub Pages&lt;/strong>. It has no backend and no path into that cloud.&lt;/p>
&lt;p>So we needed a bridge that doesn’t couple the two. Once a day the leaderboard job scores the periods we care about (month, quarter, year), writes community-only JSON, and publishes it to a public GitHub repository: &lt;a href="https://github.com/percona/community-leaderboard/tree/widget" target="_blank" rel="noopener noreferrer">percona/community-leaderboard&lt;/a>. The Hugo page embeds a JS widget that fetches those files from raw GitHub URLs. No VPN. No private API. If the internal app is offline for maintenance, the last published feed still works.&lt;/p>
&lt;p>We call that lifehack &lt;strong>GitHub as a database&lt;/strong>. It’s a boring, cacheable public artifact. An object store would work the same way. For a static site, a daily JSON dump is often simpler and safer than exposing your analytics database.&lt;/p>
&lt;p>The application breaks into a few components. Data sources are polled by indexer jobs and land in PSMDB. The same database feeds internal dashboards (explore, filter staff vs community) and a leaderboard component that builds period reports and publishes JSON to GitHub. The community site only talks to that public feed.&lt;/p>
&lt;pre class="mermaid">
flowchart TB
subgraph sources["Data sources"]
GH["GitHub"]
Forum["Forum"]
Content["Blog / content"]
end
subgraph private["Private app · corporate cloud"]
Idx["Indexer jobs"]
Mongo[("PSMDB")]
Dash["Dashboards"]
LB["Leaderboard component"]
end
subgraph public["Public"]
Feed["JSON on GitHub"]
Site["Community site · Hugo widget"]
end
GH --> Idx
Forum --> Idx
Content --> Idx
Idx -->|"index · upsert"| Mongo
Mongo -->|"read · filter · charts"| Dash
Mongo -->|"score · export"| LB
LB -->|"publish rankings"| Feed
Feed -->|"fetch JSON"| Site
style Mongo fill:#e8f5e9
style Feed fill:#fff8e1
style Site fill:#e1f5ff
style private fill:#f9f7ff
&lt;/pre>
&lt;p>Indexers and the leaderboard component run on a schedule (cron). The diagram above is the data path; the schedule is just when each box wakes up.&lt;/p>
&lt;p>On the site, the widget is more than a dumped table. You can switch periods and categories (global, GitHub, forum), open a person and see how they scored, and browse leaders without leaving Hugo. The layout had to feel like part of Community Ascent, not like an admin export pasted into a page. There is also a &lt;a href="https://percona.community/ascent/summit/" target="_blank" rel="noopener noreferrer">Global Summit&lt;/a> view (top 10 on a mountain) for the people who climb furthest.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2688" height="1672" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/leaderboard-widget-popup-details_hu_12f3080aea70d66b.webp 768w, https://percona.community/blog/2026/08/leaderboard-widget-popup-details_hu_24bc1d6aef967ca4.webp 480w, https://percona.community/blog/2026/08/leaderboard-widget-popup-details_hu_6479b1ad7447ef4d.webp 1400w" src="https://percona.community/blog/2026/08/leaderboard-widget-popup-details_hu_6479b1ad7447ef4d.webp" alt="Contributor detail popup: points by source, PRs, and forum topics" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2082" height="1484" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/leaderboard-widget-global-summit_hu_1007b0cea1339850.webp 768w, https://percona.community/blog/2026/08/leaderboard-widget-global-summit_hu_4f65a755608a5644.webp 480w, https://percona.community/blog/2026/08/leaderboard-widget-global-summit_hu_f59c7d16527a7479.webp 1400w" src="https://percona.community/blog/2026/08/leaderboard-widget-global-summit_hu_f59c7d16527a7479.webp" alt="Global Summit: top 10 worldwide on the mountain" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="public-feed-json-the-widget-reads">Public feed: JSON the widget reads&lt;/h2>
&lt;p>The Hugo widget does not guess rankings. It loads plain JSON from the public repo. On first paint it fetches &lt;code>meta.json&lt;/code> for available periods and categories, then &lt;code>{category}/{period}.json&lt;/code> for the table (for example &lt;code>global/2026.json&lt;/code>). When you open someone in the modal, it loads &lt;code>users/{period}/{user_key}.json&lt;/code> for the breakdown.&lt;/p>
&lt;p>The layout is simple:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">meta.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">global/{period}.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">github/{period}.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">forum/{period}.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">users/{period}/{user_key}.json&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>meta.json&lt;/code> is the index. The widget uses it to build the period picker:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"generated_at"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026-08-31T04:11:39+00:00"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"default_period"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"periods"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">"key"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"label"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"type"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"year"&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">"key"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026-Q3"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"label"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"Q3 2026"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"type"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"quarter"&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">"key"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026-08"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"label"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"August 2026"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">"type"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"month"&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"categories"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">"global"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">"github"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">"content"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">"forum"&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Each ranking file is one period and one category. The table reads &lt;code>top30&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"period"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"period_type"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"year"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"period_label"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"category"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"global"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"top30"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"rank"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"user_key"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"gh-example"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"display_name"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"Alex Contributor"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"avatar_url"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"https://avatars.githubusercontent.com/u/12345?v=4"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"github_login"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"alex-contrib"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"forum_username"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"points_total"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">595&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"points_github"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"points_forum"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">95&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"issues_created"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"prs_created"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"prs_merged"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"topics_created"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"replies"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"solutions"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The per-user file adds contribution lists for the detail view:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"user_key"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"gh-example"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"display_name"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"Alex Contributor"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"period"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"points_total"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">595&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"github_prs"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"title"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"Fix replication lag in operator"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"url"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"https://github.com/percona/example/pull/42"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"repo"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"percona/example"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"date"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026-07-10"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"merged"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"forum_replies"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"title"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"Slow queries after upgrade"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"url"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"https://forums.percona.com/t/example/12345"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"date"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"2026-06-15"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That is all the static site needs. Live files are in &lt;a href="https://github.com/percona/community-leaderboard/tree/widget" target="_blank" rel="noopener noreferrer">percona/community-leaderboard&lt;/a>; the widget base URL is set in the Hugo layout (&lt;code>window.LB_BASE&lt;/code>).&lt;/p>
&lt;h2 id="what-runs-every-day">What runs every day&lt;/h2>
&lt;p>Scheduled indexer jobs poll the APIs we care about, one family of sources each, and upsert documents into PSMDB. Another scheduled job builds the rankings and pushes the public feed. The web UI is how we see the store: date ranges, charts, activity tables, and a leaderboard toggle for community, staff, or all. Staff numbers matter internally. Who answered on the forum this week? Which PRs came from outside? Which community questions are still waiting? The public Mountaineers board stays community-only. Same data, different filter.&lt;/p>
&lt;p>One person is often three strings in the data. GitHub login, forum username, and a blog byline under a real name rarely match. Without linking, the same Mountaineer shows up as three rows and their points never add up. &lt;strong>Identity mapping&lt;/strong> is how we stitch that together. You can link accounts by hand in the dashboard. When a display name or handle lines up across sources, the UI also proposes a merge for approval. That is semi-automatic, not a silent auto-join. Only confirmed maps feed the public ranking.&lt;/p>
&lt;p>We develop against &lt;a href="https://docs.percona.com/percona-server-for-mongodb/8.0/index.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> in Docker locally. Same engine shape in production. I run &lt;code>docker compose up&lt;/code>, connect from the app, and start indexing. For browsing collections I use &lt;strong>MongoDB Compass&lt;/strong> on &lt;code>localhost:27018&lt;/code> (or whatever port you map). It is the fastest way to check that indexers wrote what you expect before you trust a public export.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">psmdb&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-server-mongodb:8.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./data:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">root&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">changeme&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_DATABASE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dashboard&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"27018:27017"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Pick the image tag for your CPU (&lt;code>8.0-arm64&lt;/code> on Apple Silicon, &lt;code>8.0&lt;/code> / amd64 elsewhere). No cloud dependency just to try an idea.&lt;/p>
&lt;h2 id="mongodb-as-the-use-case">MongoDB as the use case&lt;/h2>
&lt;p>If you like databases, this is the interesting middle of the story. We run &lt;a href="https://docs.percona.com/percona-server-for-mongodb/8.0/index.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> (PSMDB), but the fit is the document model. Community activity arrives as heterogeneous JSON. Issues, pull requests, forum posts, user profiles, and blog metadata don’t share one neat relational schema. Nested objects are normal, and APIs grow new fields over time. GitHub can suddenly add &lt;code>reactions&lt;/code>; the forum can expose a new &lt;code>trust_level&lt;/code> or group list. In MongoDB that usually means new keys on the document, not a migration that blocks indexing.&lt;/p>
&lt;p>PSMDB fits that. We keep sources in separate collections and store documents close to what the API returned, then add what reporting needs: stable ids and comparable dates for range queries. Upserts by source id make re-runs safe. Staff signals such as forum groups or trust level can stay on the user document while rules evolve. Indexes on the date fields keep month and quarter scans practical. Open one document and you can explain why an event counted or not.&lt;/p>
&lt;p>PSMDB is the system of record for events and profiles. Rankings are derived. The public site never connects to it. That split is useful beyond our case: a rich private store for organizers, a dumb public snapshot for the website.&lt;/p>
&lt;p>We looked at paid leaderboard products first. They were rigid on sources, weak on staff versus community, or awkward with a static site. Building around PSMDB let us encode our rules instead of bending the program to a SaaS schema.&lt;/p>
&lt;p>A few mistakes we deliberately avoided: exposing a public API that talks to the private database; scoring staff and community in one undifferentiated stream for the public board; asking Hugo to compute rankings at build time. The site only fetches JSON. Everything heavy stays behind the corporate network.&lt;/p>
&lt;h2 id="building-it-without-hundreds-of-hours">Building it without hundreds of hours&lt;/h2>
&lt;p>I could have written this stack myself. I’ve been in web development for many years and I’ve built similar pipelines before: cron jobs, API clients, admin UIs, JSON exports, frontend widgets. None of it is magic. I know how long the boring parts take when you do them by hand: wiring indexers, shaping documents, iterating on the Hugo widget layout, debugging JavaScript, mapping fields from noisy API payloads. That is often weeks of calendar time that never shows up in a program announcement.&lt;/p>
&lt;p>In about a week we had a working path from APIs to PSMDB to rankings to a public widget. An AI assistant did a lot of that scaffolding for me: boilerplate API clients for GitHub and the forum, first-pass mapping of those payloads into MongoDB collections, and early prototypes of the Preact widget (layout, period picker, loading states). I still owned the architecture and the rules; the agent compressed construction I would otherwise have typed line by line. That difference is real, and I notice it because I’m not new to this work. Judgment stayed with the community team and with Laura’s brief: what counts, who is staff, how identities merge, what the public is allowed to see, and how the board should feel on the Ascent pages.&lt;/p>
&lt;p>I used a similar approach for &lt;a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">semantic search on this site&lt;/a> with Postgres and pgvector. Different store, same idea: know what you want, let the assistant handle the repetitive build.&lt;/p>
&lt;h2 id="if-you-want-something-like-this">If you want something like this&lt;/h2>
&lt;p>You don’t need our private cloud. You need the same separation of concerns.&lt;/p>
&lt;p>Ask for scheduled indexer jobs that poll your APIs, upsert into &lt;a href="https://docs.percona.com/percona-server-for-mongodb/8.0/index.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> in Docker, keep documents close to each API’s shape, and add stable ids and dates for range queries.&lt;/p>
&lt;p>Ask for a private UI over that database with tables, charts, date filters, and an explicit community / staff / all view, behind basic auth or SSO, so you can verify fairness before anything is public. Use it to find outside PRs and unanswered community questions, not only to draw a ranking.&lt;/p>
&lt;p>Ask for a scoring job that builds period rankings, publishes community-only JSON to a public feed (GitHub raw files or object storage), and a small JS widget for your site that only reads that feed.&lt;/p>
&lt;p>Collect, understand, publish, render. Honest collection, careful filters, a simple public publish path.&lt;/p>
&lt;p>If you try it and get stuck, write to me. I’m happy to talk through what worked and what we threw away. To see the result in production, open the &lt;a href="https://percona.community/ascent/leaderboard/" target="_blank" rel="noopener noreferrer">Community Leaderboard&lt;/a>. To join the program behind it, start with Laura’s &lt;a href="https://percona.community/blog/2026/07/16/introducing-mountaineers/">Mountaineers announcement&lt;/a> or the &lt;a href="https://percona.community/ascent/mountaineers/" target="_blank" rel="noopener noreferrer">program page&lt;/a>.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>Community</category><category>Percona</category><category>Open Source</category><category>Mountaineers</category><category>Community Ascent</category><category>GitHub</category><category>MongoDB</category><category>Percona Server for MongoDB</category><category>ai</category><media:thumbnail url="https://percona.community/blog/2026/08/leaderboard-cover_hu_521f6443c8e63f54.jpg"/><media:content url="https://percona.community/blog/2026/08/leaderboard-cover_hu_d4fba8517bc95fa1.jpg" medium="image"/></item><item><title>DuckDB Speed on MySQL with dbtrail, Without a New Storage Engine</title><link>https://percona.community/blog/2026/08/26/duckdb-speed-on-mysql-without-a-new-storage-engine/</link><guid>https://percona.community/blog/2026/08/26/duckdb-speed-on-mysql-without-a-new-storage-engine/</guid><pubDate>Wed, 26 Aug 2026 11:00:00 UTC</pubDate><description>Two recent posts on the Percona blog caught my attention. In Replicating from InnoDB into a DuckDB storage engine and The DuckDB MySQL engine at 500 GB, Evgeniy Patlan wired DuckDB into mysqld as a storage engine, pointed row-based replication at it, and measured it at TPC-H scale factor 500. The numbers are striking: loads 25 times faster than InnoDB, one fifth of the disk, and the full 22-query TPC-H suite done in 186 seconds where InnoDB needed about 28 hours and never finished four of the queries.</description><content:encoded>&lt;p>Two recent posts on the Percona blog caught my attention. In &lt;a href="https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/" target="_blank" rel="noopener noreferrer">Replicating from InnoDB into a DuckDB storage engine&lt;/a> and &lt;a href="https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/" target="_blank" rel="noopener noreferrer">The DuckDB MySQL engine at 500 GB&lt;/a>, Evgeniy Patlan wired DuckDB into mysqld as a storage engine, pointed row-based replication at it, and measured it at TPC-H scale factor 500. The numbers are striking: loads 25 times faster than InnoDB, one fifth of the disk, and the full 22-query TPC-H suite done in 186 seconds where InnoDB needed about 28 hours and never finished four of the queries.&lt;/p>
&lt;p>The posts are also honest about the cost. The work is experimental. The worst bug found was silent data loss on every replicated transaction: the two-phase commit code for mixed-engine transactions dropped prepared DuckDB transactions before they committed, so replication reported success while no rows persisted. The test harness caught it and it was fixed. The row-by-row applier also cannot keep up with bulk writes on the primary.&lt;/p>
&lt;p>I want to add a second road to the same place, one that changes nothing inside mysqld. Disclosure first: I build &lt;a href="https://github.com/dbtrail/dbtrail" target="_blank" rel="noopener noreferrer">dbtrail&lt;/a>, the Apache 2.0 open source tool used below, so read this as one more point in the design space, from someone who is not neutral. Everything here ran on stock Percona Server 8.0 from Docker Hub and stock DuckDB from Homebrew, and the commands at the end reproduce all of it.&lt;/p>
&lt;h2 id="your-binlog-is-already-a-columnar-feed">Your binlog is already a columnar feed&lt;/h2>
&lt;p>The storage engine approach puts the column store inside the server, so the server must cooperate: a patched build, a new engine in the commit path, replica tables created by hand with &lt;code>ENGINE=DuckDB&lt;/code>. The other road starts from what MySQL already provides. With &lt;code>binlog_format=ROW&lt;/code> and &lt;code>binlog_row_image=FULL&lt;/code>, the binary log is a complete, ordered feed of every row change, with full before and after images. Any process can consume that feed as a replication client, the way a replica does, and install nothing on the server.&lt;/p>
&lt;p>dbtrail is that process. It does two things with the feed:&lt;/p>
&lt;ol>
&lt;li>It keeps a short, searchable window of recent events in a plain MySQL table, for investigation and recovery.&lt;/li>
&lt;li>It moves closed hours out to Parquet files, the open columnar format every analytical engine can read. DuckDB reads it natively.&lt;/li>
&lt;/ol>
&lt;p>The pipeline is &lt;code>mysqld -> binlog -> Parquet -> DuckDB&lt;/code>. No patched server, no plugin, nothing new in the commit path. And because the feed is history rather than current state, dbtrail can answer a question no replica can: what did this row look like before, and how do I undo what happened to it?&lt;/p>
&lt;h2 id="the-demo">The demo&lt;/h2>
&lt;p>One container for the source, stock image, with the settings any change-capture consumer needs (plus one line to keep the demo’s own index out of the binlog, since source and index share one server here):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d --name demo -p 13310:3306 -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>demo &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> percona/percona-server:8.0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --gtid-mode&lt;span class="o">=&lt;/span>ON --enforce-gtid-consistency&lt;span class="o">=&lt;/span>ON &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --binlog-rows-query-log-events&lt;span class="o">=&lt;/span>ON --binlog-ignore-db&lt;span class="o">=&lt;/span>bintrail_index&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>binlog_format=ROW&lt;/code> and &lt;code>binlog_row_image=FULL&lt;/code> are already the 8.0 defaults. The schema is a small shop: &lt;code>customers&lt;/code>, &lt;code>orders&lt;/code>, &lt;code>order_items&lt;/code>. Three short commands point dbtrail at it (&lt;code>init&lt;/code>, &lt;code>snapshot&lt;/code>, &lt;code>stream&lt;/code>; they are in the last section), and from there the work happens in dbtrail’s web console. Then I replayed a five-hour synthetic workload: 3,265,000 row events in all. 2.7 million INSERTs, 510 thousand UPDATEs, 55 thousand DELETEs. (A tip for demo builders: &lt;code>SET TIMESTAMP = &lt;unix_time>&lt;/code> in the writing session backdates the binlog event timestamps, so a fast replay spreads across past hours and you can watch retention behave as it would in real life.)&lt;/p>
&lt;p>The console’s Status view answers the first question an operator should ask of any change-capture pipeline: did we miss anything?&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1440" height="820" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/dbtrail-console-status_hu_d314177ed02bee4b.webp 768w, https://percona.community/blog/2026/08/dbtrail-console-status_hu_c2f852a5efd4b39.webp 480w, https://percona.community/blog/2026/08/dbtrail-console-status_hu_dc6797ddbe100111.webp 1400w" src="https://percona.community/blog/2026/08/dbtrail-console-status_hu_dc6797ddbe100111.webp" alt="dbtrail console Status view: a green no-gaps verdict, 3,265,000 events captured, and an archive tier of 7 Parquet files totaling 26 MB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The green banner is a verdict, not a guess: dbtrail tracks continuity across the captured range, and it says plainly that this is not a liveness check. On my MacBook the capture stream held about 18,000 events per second while it tailed the binlog. That answers the applier problem from the first post: dbtrail does not push rows through the storage engine API one at a time, it batches them into an index, so bulk writes on the primary are absorbed rather than queued.&lt;/p>
&lt;h2 id="from-hot-partitions-to-parquet">From hot partitions to Parquet&lt;/h2>
&lt;p>The index table is range-partitioned by hour. dbtrail archives each closed hour to zstd-compressed Parquet, checks the result, and only then drops the partition. Retention on the expensive tier becomes hours, not months. The demo moved all 3.26 million events out in 14 seconds, and the Status view above already showed the result: 7 archive files, 26 MB. Here is that number next to what the same events cost in InnoDB:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2160" height="780" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/duckdb-mysql-storage_hu_b12af0f884c3df0c.webp 768w, https://percona.community/blog/2026/08/duckdb-mysql-storage_hu_dfe2e6b584b74900.webp 480w, https://percona.community/blog/2026/08/duckdb-mysql-storage_hu_f49bae55fc9733cb.webp 1400w" src="https://percona.community/blog/2026/08/duckdb-mysql-storage_hu_f49bae55fc9733cb.webp" alt="Bar chart: the same 3,265,000 events take 2,822 MB as an InnoDB table and 26 MB as zstd Parquet" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Do not take that 100x as a general truth. Synthetic demo data repeats itself and compresses far too well, and the InnoDB figure includes the primary key and secondary indexes that make the hot tier searchable. On production data expect about one order of magnitude. The direction matches what Evgeniy measured at 500 GB, where the DuckDB engine held TPC-H in 26 percent of the raw CSV size and InnoDB needed 135 percent. Column formats fit this data. Row formats do not.&lt;/p>
&lt;p>One &lt;code>mydumper&lt;/code> pass adds the state side: a baseline snapshot of the tables themselves, also stored as Parquet. For the demo shop that was 2.6 million rows in 8 MB, done in under four seconds. dbtrail uses baselines to rebuild full tables and single rows at a point in time; here they also give DuckDB something current to query.&lt;/p>
&lt;h2 id="stock-duckdb-no-plugins">Stock DuckDB, no plugins&lt;/h2>
&lt;p>The archive is just files, so the query engine does not have to be dbtrail. One command, &lt;code>bintrail views&lt;/code> (bintrail is the name of dbtrail’s CLI binary), writes a single SQL file of DuckDB view definitions over the layout it knows about: an &lt;code>events&lt;/code> view across every archived partition, and one &lt;code>state_&lt;schema>_&lt;table>&lt;/code> view per table of the newest baseline:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">bintrail views --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --baseline-dir /data/baselines --out views.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">duckdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">D .read views.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That is the whole integration. dbtrail never opens DuckDB and never runs what it prints; the file is plain SQL you can read before you use it. From there, use any DuckDB you like: the CLI, a notebook, a BI tool’s connector. Same laptop, same data, same queries on both engines:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2160" height="1456" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/duckdb-mysql-query-latency_hu_c12061f9705f52a.webp 768w, https://percona.community/blog/2026/08/duckdb-mysql-query-latency_hu_aa602358f3aebed0.webp 480w, https://percona.community/blog/2026/08/duckdb-mysql-query-latency_hu_65d4d0d857c5dd52.webp 1400w" src="https://percona.community/blog/2026/08/duckdb-mysql-query-latency_hu_65d4d0d857c5dd52.webp" alt="Dot plot on a log scale, two groups: three queries over the event history and two over current state; MySQL InnoDB takes 0.4 to 4.6 seconds, stock DuckDB over Parquet takes 0.04 to 0.16 seconds, 11 to 112 times faster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now the fine print, because a benchmark without it is just marketing. The chart has two groups. The first group queries the event history: those scans hit a 2.8 GB index with the container’s stock 128 MB buffer pool, so InnoDB paid for disk reads, and a bigger pool narrows that gap. The second group runs over current state and is the fair comparison: I grew the pool to 4 GB, both tables sat fully in cache, and the 11x and 20x that remain come from the design of the engine, not from the disk. Both engines returned the same results, down to the last decimal. That is a free integrity check: two independent systems read the same history and agreed.&lt;/p>
&lt;p>These are laptop numbers, and I did not run TPC-H. For the ceiling of what columnar execution does at 500 GB, read Evgeniy’s second post. My point is the floor: 2.8 GB of freshly rotated history sat on my laptop as 26 MB of open files, and a stock engine answers questions over them in tens of milliseconds. Nobody patched anything to get here.&lt;/p>
&lt;h2 id="the-part-a-replica-cannot-do">The part a replica cannot do&lt;/h2>
&lt;p>A DuckDB replica holds current state. dbtrail holds what happened. This is the same data the analytics just scanned, now in the console’s Events view, filtered to one order:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1493" height="812" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/dbtrail-console-events-diff_hu_659ec301bd517c37.webp 768w, https://percona.community/blog/2026/08/dbtrail-console-events-diff_hu_4f67b5458c455f04.webp 480w, https://percona.community/blog/2026/08/dbtrail-console-events-diff_hu_4818001f8a06f677.webp 1400w" src="https://percona.community/blog/2026/08/dbtrail-console-events-diff_hu_4818001f8a06f677.webp" alt="dbtrail console Events view: the three events of order 997000, INSERT then UPDATE then DELETE, with the UPDATE expanded to show full before and after images and an Undo this change button" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Three events tell the row’s whole story: created, cancelled, deleted, each with its GTID and the connection that did it. The expanded UPDATE shows the full before and after images dbtrail keeps for every change.&lt;/p>
&lt;p>Here is the detail that surprises people: when I took that screenshot, the MySQL side of the index held zero rows. Rotation had dropped every partition. The console read the answer from the Parquet tier in under 200 milliseconds. The Undo button works from there too:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1493" height="812" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/dbtrail-console-restore-undo_hu_9b745d4a82b53d86.webp 768w, https://percona.community/blog/2026/08/dbtrail-console-restore-undo_hu_3712f2b5bfae1e0e.webp 480w, https://percona.community/blog/2026/08/dbtrail-console-restore-undo_hu_a6b3c278c6e1ea4a.webp 1400w" src="https://percona.community/blog/2026/08/dbtrail-console-restore-undo_hu_a6b3c278c6e1ea4a.webp" alt="dbtrail console Restore view: one click on Undo this change produced reversal.sql, a reviewed-before-applied script that reverses exactly one UPDATE, with Copy and Download buttons" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>One click wrote &lt;code>reversal.sql&lt;/code>: a script that puts the row back exactly as it was, tagged with the GTID it reverses. Nothing runs on its own; you read the script, then you apply it. The same Restore view takes a whole table and a time window: the demo’s 5,000 deleted orders came back as 5,000 INSERT statements, generated in 0.2 seconds, from files, while the database that held that history no longer existed.&lt;/p>
&lt;p>A column store fed by your binlog does not have to be a replica. It can be a time machine.&lt;/p>
&lt;h2 id="trade-offs-honestly">Trade-offs, honestly&lt;/h2>
&lt;p>Neither road wins outright.&lt;/p>
&lt;p>&lt;strong>Where the storage engine is better: its DuckDB copy is always current, and it speaks MySQL protocol.&lt;/strong> A DuckDB engine on a replica runs seconds behind the primary, and existing BI tools connect to it unchanged. dbtrail’s capture also runs seconds behind, and its console and CLI query that fresh index directly. What waits is the copy DuckDB reads: the events view covers the hours already rotated to Parquet, and the state views show the latest baseline, so that side is as current as your rotation and baseline schedule. If you need a BI tool on MySQL protocol reading a complete, current copy, Evgeniy’s architecture, or a product like HeatWave, aims at exactly that.&lt;/p>
&lt;p>&lt;strong>Where staying outside the server is better: risk and history.&lt;/strong> The hardest place to change a database is the commit path, and a storage engine lives there. The 2PC bug shows the kind of failure that layer produces, and it took a dedicated test harness to find it, because it was silent. A replication client cannot corrupt a commit. Its failure modes are lag and gaps, and dbtrail reports gaps as a first-class verdict rather than hoping. History is the other half: audit, point-in-time rebuilds, and row-level undo all come from the same files the analytics read.&lt;/p>
&lt;p>&lt;strong>Shared constraints.&lt;/strong> Both roads need &lt;code>ROW&lt;/code> format with &lt;code>FULL&lt;/code> row images. Both need primary keys to apply or reverse UPDATEs and DELETEs. Both leave the source of truth in InnoDB, untouched. On maturity: the engine posts describe an experiment and say so; dbtrail is released and versioned (v0.65.0 as I write this) but young, and you should question my numbers the same way I question everyone else’s.&lt;/p>
&lt;p>Both posts and this one agree on the base facts: row stores are the wrong shape for analytical scans, DuckDB fits MySQL-shaped data well, and the open question is where the column store should live. Evgeniy shows what you gain when the server cooperates. dbtrail shows what you get when you leave the server alone.&lt;/p>
&lt;h2 id="reproduce-it">Reproduce it&lt;/h2>
&lt;p>dbtrail binaries are on the &lt;a href="https://github.com/dbtrail/dbtrail/releases" target="_blank" rel="noopener noreferrer">releases page&lt;/a>; DuckDB comes from your package manager. The web console ships as its own binary, &lt;code>bintrail-console&lt;/code>, and can also run capture and console together as one daemon (&lt;code>bintrail-console watch&lt;/code>).&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. A source with binlogs (ROW + FULL are 8.0 defaults)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run -d --name demo -p 13310:3306 -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>demo &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> percona/percona-server:8.0 --gtid-mode&lt;span class="o">=&lt;/span>ON --enforce-gtid-consistency&lt;span class="o">=&lt;/span>ON &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --binlog-rows-query-log-events&lt;span class="o">=&lt;/span>ON --binlog-ignore-db&lt;span class="o">=&lt;/span>bintrail_index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Create your schema, then point dbtrail at it&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail init --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail snapshot --source-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$SRC&lt;/span>&lt;span class="s2">"&lt;/span> --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --schemas shop
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail stream --source-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$SRC&lt;/span>&lt;span class="s2">"&lt;/span> --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --server-id &lt;span class="m">4444&lt;/span> --schemas shop &lt;span class="p">&amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail-console serve --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --baseline-dir /data/baselines &lt;span class="p">&amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Run your workload, then tier the history out and take a baseline&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail rotate --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --retain 7d --archive-dir /data/archives
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail dump --source-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$SRC&lt;/span>&lt;span class="s2">"&lt;/span> --output-dir /data/dump --schemas shop
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail baseline --input /data/dump --output /data/baselines
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. Hand the whole thing to DuckDB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bintrail views --index-dsn &lt;span class="s2">"&lt;/span>&lt;span class="nv">$IDX&lt;/span>&lt;span class="s2">"&lt;/span> --baseline-dir /data/baselines --out views.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">duckdb -c &lt;span class="s2">".read views.sql"&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -c &lt;span class="s2">"SELECT table_name, event_type, COUNT(*) FROM events GROUP BY 1,2;"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you try dbtrail, tell me where it fails as much as where it works well. Issues and pull requests are open at &lt;a href="https://github.com/dbtrail/dbtrail" target="_blank" rel="noopener noreferrer">github.com/dbtrail/dbtrail&lt;/a>, and I am around in the Percona Community Slack.&lt;/p></content:encoded><author>Daniel Guzman Burgos</author><category>MySQL</category><category>DuckDB</category><category>Parquet</category><category>Opensource</category><category>binlog</category><category>analytics</category><category>Percona Server</category><category>dbtrail</category><media:thumbnail url="https://percona.community/blog/2026/08/duckdb-mysql-no-engine-cover_hu_89756b4f4249684a.jpg"/><media:content url="https://percona.community/blog/2026/08/duckdb-mysql-no-engine-cover_hu_aa14e573d5c028ac.jpg" medium="image"/></item><item><title>Hackorum Update: What's New Since February</title><link>https://percona.community/blog/2026/08/20/hackorum-update-whats-new-since-february/</link><guid>https://percona.community/blog/2026/08/20/hackorum-update-whats-new-since-february/</guid><pubDate>Thu, 20 Aug 2026 10:00:00 UTC</pubDate><description>Back in February, I wrote about Hackorum, a forum style web view of the pg-hackers mailing list. If you missed that post, you can read it here first. It turns the mailing list into something that reads and navigates a bit more like a modern forum, while the mailing list itself stays the source of truth.</description><content:encoded>&lt;p>Back in February, I wrote about &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">Hackorum&lt;/a>, a forum style web view of the pg-hackers mailing list. If you missed that post, you can &lt;a href="https://percona.community/blog/2026/02/02/hackorum-a-forum-style-view-of-pg-hackers/" target="_blank" rel="noopener noreferrer">read it here&lt;/a> first. It turns the mailing list into something that reads and navigates a bit more like a modern forum, while the mailing list itself stays the source of truth.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1800" height="873" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/hackorum-update-topic-index_hu_25dfccde313f3d1e.webp 768w, https://percona.community/blog/2026/08/hackorum-update-topic-index_hu_427a1c8b80722fbc.webp 480w, https://percona.community/blog/2026/08/hackorum-update-topic-index_hu_8413875a6b00bf8f.webp 1400w" src="https://percona.community/blog/2026/08/hackorum-update-topic-index_hu_8413875a6b00bf8f.webp" alt="Hackorum topic index showing pg-hackers threads with commitfest, patch and CI status icons" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>A lot has happened since then. We also talked about the project at the PostgreSQL meetup in Berlin in March, &lt;a href="https://www.postgresql.eu/events/pgconfde2026/schedule/session/7760-modernising-postgres-community-communication-with-hackorum/" target="_blank" rel="noopener noreferrer">at pgconf.de&lt;/a>, and in a lightning talk at pgconf.dev, and a good chunk of what was still on the roadmap back then is live today. We also posted &lt;a href="https://www.youtube.com/watch?v=onQQJzQ8Qlw" target="_blank" rel="noopener noreferrer">a video introduction to Hackorum&lt;/a>, if you prefer to see it in action rather than read about it. This post is a follow up to walk through what changed, what is new, and what we are looking at next.&lt;/p>
&lt;h2 id="a-quick-recap">A quick recap&lt;/h2>
&lt;p>Hackorum is an Open Source project from the community to be used by the community and hosted on &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">hackorum.dev&lt;/a>. The code is on &lt;a href="https://github.com/hackorum-dev/hackorum" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>. If you are new here, the short version that it syncs postgres mailing lists in the background, keeps read status, stars, tags and notes for you, and adds commitfest context, commit and patch history, and contributor info directly next to the discussion.&lt;/p>
&lt;p>Everything below is new for users since the last post. I am leaving out internal and admin only changes, since those do not affect how you use the site day to day.&lt;/p>
&lt;h2 id="commits-linked-back-to-their-discussion">Commits linked back to their discussion&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1600" height="604" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/hackorum-update-commit-profile_hu_42a84e9523ed37a0.webp 768w, https://percona.community/blog/2026/08/hackorum-update-commit-profile_hu_62783d71bba8663e.webp 480w, https://percona.community/blog/2026/08/hackorum-update-commit-profile_hu_2001c1ff82193dc2.webp 1400w" src="https://percona.community/blog/2026/08/hackorum-update-commit-profile_hu_2001c1ff82193dc2.webp" alt="Contributor profile showing commit credits and how many patch threads landed" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Hackorum now links pushed commits back to the thread and patch discussion that produced them. Contributor profile pages have a new commit history tab, so you can see someone’s message activity and their commits in one place. There is also a &lt;a href="https://www.postgresql.org/message-id/CAAKRu_Z42AAq7N%3DusSS3UPMtXqbVvsQzktNmH1X20oypyqA_Xg%40mail.gmail.com" target="_blank" rel="noopener noreferrer">community proposal on pgsql-www&lt;/a> for public contributor profile pages, with the PostgreSQL Contributors Committee looking to recognize specific contributions beyond code, for example volunteering at a conference. If that lands, it would be a natural fit to surface on this profile too.&lt;/p>
&lt;h2 id="patch-ci-results-now-public">Patch CI results, now public&lt;/h2>
&lt;img src="https://percona.community/blog/2026/08/hackorum-update-ci-status.png" alt="CI status card on a thread, showing apply, build and test results" style="display:block;margin:1rem auto;width:50%;border-radius:6px;">
&lt;p>Patches attached to a thread are automatically applied, rebased when master moves, and built and tested with the PostgreSQL test suite. Those results used to be an internal experiment, they are now public for everyone:&lt;/p>
&lt;ul>
&lt;li>CI status icons in the topic index, and full detail in tabs on the thread itself&lt;/li>
&lt;li>A CI dashboard at &lt;a href="https://hackorum.dev/ci" target="_blank" rel="noopener noreferrer">hackorum.dev/ci&lt;/a>&lt;/li>
&lt;li>A per topic CI history view&lt;/li>
&lt;li>A stats overview page&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1600" height="1156" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/hackorum-update-ci-history_hu_4e3daadb3f41a337.webp 768w, https://percona.community/blog/2026/08/hackorum-update-ci-history_hu_8869f8c50411f4c5.webp 480w, https://percona.community/blog/2026/08/hackorum-update-ci-history_hu_90e1159e5d48f16a.webp 1400w" src="https://percona.community/blog/2026/08/hackorum-update-ci-history_hu_90e1159e5d48f16a.webp" alt="Patch CI history for a thread, showing every tracked patchset version and its result" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We reapply the latest version of each patch once a day against current master. If it stops applying, we do not give up right away, we keep retrying for 30 more days, so one bad day does not retire a patch. Only after 30 days of failing do we mark it retired, meaning the base is too old, and stop trying until a new version is posted.&lt;/p>
&lt;p>Once something actually gets committed, there is nothing left to test against. There are two ways this shows up. If the committer changed the patch before committing it, the old version no longer applies and Hackorum shows it as no longer matching. If the committer applied it exactly as submitted, the diff against the committed version on GitHub is empty, so we know it went in as-is. Either way, we stop running CI on that thread until a new patch shows up. Some threads get committed in stages, with fixups or follow up patches, so if a new patchset lands afterward, we pick CI back up and start testing it.&lt;/p>
&lt;p>One more detail worth calling out is that Hackorum highlights patches that are already committed but still have an open commitfest entry, so those are easy to spot and clean up. That is currently a big issue and might really help to get this easier updated in the future. A future integration with the commitfest might be thinkable.&lt;/p>
&lt;h2 id="support-for-more-mailing-lists">Support for more mailing lists&lt;/h2>
&lt;img src="https://percona.community/blog/2026/08/hackorum-update-mailinglist-badge.png" alt="Mailing list badges next to each thread, showing hackers, bugs and docs" style="display:block;margin:1rem auto;width:50%;border-radius:6px;">
&lt;p>Hackorum used to be pg-hackers only. It now also ingests pgsql-bugs, pgsql-docs, pgsql-general and pgsql-patches, with a badge next to each thread so you can see at a glance which list a message came from. We also tried pgsql-committers, it is imported up to around May/June, but we paused it for now. It is mostly terse commit notifications, and without a way to hide a list by default yet, it added more noise than value. We are planning to add more lists, so let us know if you are missing one.&lt;/p>
&lt;h2 id="saved-searches">Saved searches&lt;/h2>
&lt;img src="https://percona.community/blog/2026/08/hackorum-update-saved-searches.png" alt="Saved searches in the sidebar, grouped into global and personal searches" style="display:block;margin:1rem auto;width:30%;border-radius:6px;">
&lt;p>The advanced search from the last post is still there, but you no longer have to retype the same query every time. You can save a search, personally or shared with your team, and pin it to your sidebar. We also ship a few global saved searches out of the box.&lt;/p>
&lt;h2 id="ignore-threads-you-do-not-care-about">Ignore threads you do not care about&lt;/h2>
&lt;p>A small one, but a popular request was that you can now ignore a thread from the topic list with one click. Ignored threads disappear from your views and search results by default, and you can always list what you have ignored, or bring one back.&lt;/p>
&lt;h2 id="sending-email-from-hackorum">Sending email from Hackorum&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1100" height="387" sizes="(max-width: 1100px) 100vw, 1100px" srcset="https://percona.community/blog/2026/08/hackorum-update-reply-composer_hu_2bb37753a9f848bf.webp 768w, https://percona.community/blog/2026/08/hackorum-update-reply-composer_hu_15471d5f8360de60.webp 480w, https://percona.community/blog/2026/08/hackorum-update-reply-composer_hu_c1e96db47ee957f6.webp 1100w" src="https://percona.community/blog/2026/08/hackorum-update-reply-composer_hu_c1e96db47ee957f6.webp" alt="Reply composer for sending a message to the mailing list from Hackorum" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This was the top item on the “planned” list in February, and it is here now. You can reply to a thread directly from Hackorum, including reply all, with a Thunderbird style selective quote so you only quote the part you are actually replying to. There is a drafts sidebar so you can keep several replies in progress, and a “My emails” page that shows everything you have sent through the site.&lt;/p>
&lt;p>This is currently limited to @gmail accounts and being under active testing. If you like early access, just reach out so we can activate it for your user.&lt;/p>
&lt;h2 id="everyday-improvements">Everyday improvements&lt;/h2>
&lt;p>None of these are headline features on their own, but you will notice them:&lt;/p>
&lt;ul>
&lt;li>Long threads load in batches, so opening a huge discussion is noticeably faster&lt;/li>
&lt;li>A “jump to latest” button, and a setting to jump straight to your first unread message when you open a thread&lt;/li>
&lt;li>Read messages can collapse automatically if you prefer a shorter view&lt;/li>
&lt;li>Patch diffs now show inline line stats and highlighting&lt;/li>
&lt;li>Sessions now stay signed in for 30 days across devices&lt;/li>
&lt;li>The light/dark theme toggle is available everywhere, even before you sign in&lt;/li>
&lt;/ul>
&lt;h2 id="mobile-got-a-real-pass">Mobile got a real pass&lt;/h2>
&lt;img src="https://percona.community/blog/2026/08/hackorum-update-mobile-view.png" alt="Hackorum topic list on mobile" style="display:block;margin:1rem auto;width:60%;border-radius:6px;">
&lt;p>The mobile experience got a lot of attention: a proper burger menu, a quick shortcut to threads you starred, working swipe back and forward gestures, and layout fixes for tablets and foldables.&lt;/p>
&lt;h2 id="what-we-are-working-on-next">What we are working on next&lt;/h2>
&lt;p>We are looking into AI generated summaries for longer threads, so you can get up to speed on a big discussion without reading every message.&lt;/p>
&lt;h2 id="try-it-and-share-feedback">Try it and share feedback&lt;/h2>
&lt;p>If you want to take a look, just go to &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">https://hackorum.dev/&lt;/a>.&lt;/p>
&lt;p>The repository, including a simple dev setup, is here: &lt;a href="https://github.com/hackorum-dev/hackorum" target="_blank" rel="noopener noreferrer">https://github.com/hackorum-dev/hackorum&lt;/a>&lt;/p>
&lt;p>Want to chat with us, join the hackorum channel on the &lt;a href="https://discordapp.com/channels/1258108670710124574/1471524461374083186" target="_blank" rel="noopener noreferrer">PostgreSQL Hacking Discord&lt;/a>.&lt;/p>
&lt;p>Is this useful? What is missing? What would you change? Bug reports, feature requests, and contributions are all welcome: &lt;a href="https://github.com/hackorum-dev/hackorum/issues" target="_blank" rel="noopener noreferrer">https://github.com/hackorum-dev/hackorum/issues&lt;/a>&lt;/p>
&lt;p>Thanks for taking a look, and we appreciate any feedback.&lt;/p></content:encoded><author>Kai Wagner</author><category>Percona</category><category>PostgreSQL</category><category>Community</category><category>Mailing Lists</category><category>Open Source</category><category>pg_kwagner</category><media:thumbnail url="https://percona.community/blog/2026/08/hackorum-update-banner_hu_200548ef9ab6023b.jpg"/><media:content url="https://percona.community/blog/2026/08/hackorum-update-banner_hu_d237ba2909b98d3c.jpg" medium="image"/></item><item><title>The curious case of timezone inconsistencies between PgBouncer and Patroni Cluster</title><link>https://percona.community/blog/2026/08/20/timezone-inconsistencies-pgbouncer-patroni/</link><guid>https://percona.community/blog/2026/08/20/timezone-inconsistencies-pgbouncer-patroni/</guid><pubDate>Thu, 20 Aug 2026 00:00:00 UTC</pubDate><description>I would like to introduce here the curious case that I had on a Patroni Cluster in production, after a migration from Oracle, with timezone differences between what was written from an application log and what was set in PostgreSQL.</description><content:encoded>&lt;p>I would like to introduce here the curious case that I had on a Patroni Cluster in production, after a migration from Oracle, with timezone differences between what was written from an application log and what was set in PostgreSQL.&lt;/p>
&lt;p>First of all a quick description of our setup. We have a two nodes Patroni cluster on Open Stack virtual machines, PostgreSQL 18, with leader and a read only replica, so we write only on one node and use the second for data extractions. All applications use Kubernetes pods that are also setup in Open Stack and access PostgreSQL via PgBouncer connection pooler. We do have also some Java application making use of their own Hikari connection pooler. All on premises.&lt;/p>
&lt;p>The application, being a legacy one, needed to use all the time data types in the timezone of the customer, so I had to setup the timezone of the PostgreSQL cluster different from the one of the VMs, that is in UTC. That was the first error as I set it up using a simple ALTER SYSTEM SET TIMEZONE followed by a SELECT pg_reload_conf(). This changes the timezone on the postgresql.auto.conf configuration file, which is totally good if you have a normal single node or replica PostgreSQL but not with a Patroni Cluster! I will return on this point in a moment.&lt;/p>
&lt;p>In itself the migration was fine, but we had to tailor a little bit the resources assigned to the nodes as unfortunately we were not able to test, prior to the migration, some parts of the application. So we had to increase RAM on the VMs and modify shared_buffers parameter, doing a restart of the PostgreSQL cluster one node at a time.&lt;/p>
&lt;p>Unfortunately the restart wiped out the timezone configuration that I did, as I should have added this through a patronictl edit-config command as I did for the shared_buffers. At least we recognized quickly the problem as the timezone of the whole Patroni Cluster reverted back to UTC. This time I changed it modifying the Patroni Cluster configuration, using the following patronictl command edit-config:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="564" height="20" sizes="(max-width: 564px) 100vw, 564px" srcset="https://percona.community/blog/2026/08/pgbouncer-patroni-fig1-edit-config_hu_458010f04e6fe1a.webp 480w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig1-edit-config_hu_50afa8d9c5860e10.webp 564w" src="https://percona.community/blog/2026/08/pgbouncer-patroni-fig1-edit-config_hu_50afa8d9c5860e10.webp" alt="patronictl edit-config command" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Fig. 1 – patronictl edit-config command&lt;/em>&lt;/p>
&lt;p>And adding the correct timezone:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="507" height="47" sizes="(max-width: 507px) 100vw, 507px" srcset="https://percona.community/blog/2026/08/pgbouncer-patroni-fig2-timezone-value_hu_98bef5f32f392e47.webp 480w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig2-timezone-value_hu_7a688fd3a7312933.webp 507w" src="https://percona.community/blog/2026/08/pgbouncer-patroni-fig2-timezone-value_hu_7a688fd3a7312933.webp" alt="timezone Africa/Lagos in Patroni config" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="595" height="400" sizes="(max-width: 595px) 100vw, 595px" srcset="https://percona.community/blog/2026/08/pgbouncer-patroni-fig3-apply-config_hu_1bab6c5f5edd5ec4.webp 480w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig3-apply-config_hu_665a6c1581ac5f4f.webp 595w" src="https://percona.community/blog/2026/08/pgbouncer-patroni-fig3-apply-config_hu_665a6c1581ac5f4f.webp" alt="Apply Patroni config change" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Fig. 2 – Result of edit-config command&lt;/em>&lt;/p>
&lt;p>Confirmed issuing a show-config:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="510" height="45" sizes="(max-width: 510px) 100vw, 510px" srcset="https://percona.community/blog/2026/08/pgbouncer-patroni-fig4-show-config_hu_560b83ad62491418.webp 480w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig4-show-config_hu_66d45dfda5521c10.webp 510w" src="https://percona.community/blog/2026/08/pgbouncer-patroni-fig4-show-config_hu_66d45dfda5521c10.webp" alt="show-config timezone Africa/Lagos" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Fig. 3 – Result of show-config command&lt;/em>&lt;/p>
&lt;p>After that we resumed normal operations and all seemed fine, problem solved, no big issue or disruptions to our customer.&lt;/p>
&lt;p>The day after I got contacted by one developer telling me that PostgreSQL in production is still in UTC. I checked it immediately and it was instead in WAT, confirmed issuing:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="860" height="955" sizes="(max-width: 860px) 100vw, 860px" srcset="https://percona.community/blog/2026/08/pgbouncer-patroni-fig5-pgadmin-timezone_hu_ba45c564581bee9b.webp 768w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig5-pgadmin-timezone_hu_db37f329446942ff.webp 480w, https://percona.community/blog/2026/08/pgbouncer-patroni-fig5-pgadmin-timezone_hu_3702cf3278763eca.webp 860w" src="https://percona.community/blog/2026/08/pgbouncer-patroni-fig5-pgadmin-timezone_hu_3702cf3278763eca.webp" alt="pgAdmin show timezone Africa/Lagos" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Fig. 4 – Screenshot of show timezone in pgAdmin&lt;/em>&lt;/p>
&lt;p>Then the developers sent me the results of a query done on an application log table showing a clear inconsistency of timestamps, some were correct and some with UTC timezone! That is a very bad scenario, where you do not have a clear indication, all wrong or all correct, but mixed results.&lt;/p>
&lt;p>I started suspecting that there was something wrong on the application side as all that I saw on the database was correct, except some of the values that were recorded. So my next move was to ask devops to restart one by one all the application pods (remember that we are using Kubernetes).&lt;/p>
&lt;p>This action brought mixed results as at the beginning it seemed that problem was solved and no rows with timestamp with wrong timezone was inserted…but after some time the first rows with UTC started resurfacing again! That was weird so I started digging a little bit more and searched our setup and our structure, as I was sure that the PostgreSQL database in itself was not the culprit.&lt;/p>
&lt;p>As you may have guessed at this point there was one Elephant in the room that I had not yet investigated (and no was not PostgreSQL Elephant, AKA Slonik): PgBouncer! In fact I had totally left out of the picture the connection pooler as I thought that it was installed in kubernetes as a sidecar of the application pods, so expecting that a restart of the pods would have affected also PgBouncer.&lt;/p>
&lt;p>As that was the architectural schema that was decided some years ago for our applications. Turned out that this was not the case!&lt;/p>
&lt;p>Let’s make a quick excursus on why it is extremely important to have a connection pooler in PostgreSQL, the pool mode parameter of PgBouncer and the possible ways of installing and configuring it. These points are important to understand our case.&lt;/p>
&lt;p>PostgreSQL connections are costly, mainly in terms of RAM, so much that there is a parameter max_connections to limit the maximum number of sessions, both active and idle. The default value is 100 which obviously is quite low, but rising it means that we need to ensure that we have enough RAM available, as the overhead for each connection is roughly 10 Mb. Bringing this parameter up for example to 500 means having already 5 Gb of RAM used just for idle connections, without even issuing any query!&lt;/p>
&lt;p>Here is where connection poolers come to the rescue, as they are capable of recycling the connections, so that applications do not need to open a new connection each time they need to query the database. The pool maintains a fixed set of open connections, all requests borrow a connection, use it, and return it to the pool. The connection itself is never closed between requests, it stays open and ready so that the next request picks it up instantly.&lt;/p>
&lt;p>PgBouncer is the de facto standard for connection pooling in PostgreSQL as it is lightweight, easy to configure and maintain. There is a very important setup choice to be made when installing PgBouncer: the pool mode, that decides the behaviour of the pooler. There are 3 possible choices: Session – Transaction – Statement.&lt;/p>
&lt;p>Session Pooling: Assigns a server connection to a client as soon as it connects and holds it until the client disconnects. It acts almost like a direct connection to PostgreSQL, supporting all session features, but offers the least connection reuse. This is the most safe way to configure PgBouncer, and since we use some session features like Prepared Statements, this was our choice.&lt;/p>
&lt;p>Transaction Pooling: Assigns a server connection only for the duration of a single BEGIN … COMMIT/ROLLBACK transaction. Once the transaction completes, the connection goes back to the pool for another client to use.&lt;/p>
&lt;p>Statement Pooling: Assigns a server connection for a single SQL statement, returning it immediately after execution. It allows maximum reuse, but breaks multi-statement transactions (BEGIN … COMMIT) and session features.&lt;/p>
&lt;p>One last word on what are the recommended ways to install PgBouncer: first of all it should be on a separate server respect the PostgreSQL cluster, then in a Kubernetes environment, such as ours, it can be installed as a sidecar to Kubernetes pods (which is the recommended way for most cases) or as a separate deployment in its own pod. Turns out that we had installed PgBouncer in this last way instead of having it in the same pod of the application.&lt;/p>
&lt;p>Now that we have a complete picture, let’s go back to our case. Since our PgBouncer was installed as a standalone pod and not in sidecar, this become my primary culprit, searching a little bit I found out that there is the possibility in PgBouncer that if a database is created or its timezone is altered via ALTER DATABASE … SET timezone, PgBouncer does not properly invalidate its internal startup-parameter cache.&lt;/p>
&lt;p>In our case the timezone was changed through Patroni, not ALTER DATABASE, but PgBouncer still kept a stale cached TimeZone.&lt;/p>
&lt;p>So when a connection is recycled:&lt;/p>
&lt;ul>
&lt;li>PostgreSQL natively sets the timezone to Africa/Lagos.&lt;/li>
&lt;li>PgBouncer looks at its cached baseline state for that database/user profile from when it first booted up (which was reset to UTC when we restarted the Patroni nodes remember!).&lt;/li>
&lt;li>PgBouncer then subtly injects a session-level override back to the client, effectively masking the database’s actual default settings.&lt;/li>
&lt;/ul>
&lt;p>It was exactly our case, as the restart of Patroni Cluster nodes returned all the system to UTC, then we modified it again to WAT, but we never restarted the PGBouncer pod, so we still had some sessions in the pool with UTC timezone. That explained also the mix between correct and wrong timezones in the timestamps, it depended from the session with which the row was inserted, if it was one of the old ones we had UTC.&lt;/p>
&lt;p>The fix at this point was obvious: clear the cache of PgBouncer. This can be done with a simple restart of that pod or a reload of the configuration. In our case we restarted PgBouncer and surely this action solved all our timezone problems as from that point we had only timestamps with correct timezone, confirming the above scenario.&lt;/p>
&lt;p>So this was the curious case of timezone inconsistencies between PostgreSQL and PgBouncer, in the end was easily solved and not disruptive, it was due mainly to a couple of errors and assumptions that I did and that were surely avoidable, that’s why I wanted to bring attention to this subject: prevent others doing my errors and recognize faster the issue.&lt;/p>
&lt;p>Summarizing all in a few key points of lessons learned:&lt;/p>
&lt;ul>
&lt;li>After changing timezone in Patroni, always restart or RECONNECT PgBouncer so the pool doesn’t keep stale session settings.&lt;/li>
&lt;li>Remember to always change timezone in a Patroni cluster using patronictl command edit-config and not ALTER SYSTEM SET TIMEZONE.&lt;/li>
&lt;li>In a Kubernetes environment install PgBouncer as a sidecar to Kubernetes pods instead of a standalone pod.&lt;/li>
&lt;/ul>
&lt;p>Hope it will help some of you!&lt;/p>
&lt;p>&lt;em>This post is part of the &lt;a href="https://percona.community/blog/write-for-percona-community/">Percona Community Writers Program&lt;/a>.&lt;/em>&lt;/p></content:encoded><author>Andrea Gnemmi</author><category>PostgreSQL</category><category>Patroni</category><category>PgBouncer</category><category>Kubernetes</category><category>High Availability</category><category>Community</category><media:thumbnail url="https://percona.community/blog/2026/08/andrea-gnemmi-pgbouncer-patroni-cover_hu_59b13c21a659efa1.jpeg"/><media:content url="https://percona.community/blog/2026/08/andrea-gnemmi-pgbouncer-patroni-cover_hu_95fbdb4137018c36.jpeg" medium="image"/></item><item><title>Herding Goats Across Continents: How We Took 100 People From Around the World to Turkey for an Offsite (and Lived to Tell the Tale)</title><link>https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/</link><guid>https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/</guid><pubDate>Thu, 06 Aug 2026 11:00:00 UTC</pubDate><description>Every great journey needs a guide.</description><content:encoded>&lt;p>Every great journey needs a guide.&lt;/p>
&lt;p>Ours just happened to be a mountain goat.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1500" height="1999" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/turkey-goat-lego_hu_3d01dd13e473d5e6.webp 768w, https://percona.community/blog/2026/08/turkey-goat-lego_hu_36dc0a36dc640106.webp 480w, https://percona.community/blog/2026/08/turkey-goat-lego_hu_880e747cea7a6659.webp 1400w" src="https://percona.community/blog/2026/08/turkey-goat-lego_hu_880e747cea7a6659.webp" alt="LEGO mountain goat with Percona branding on a laptop" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Fresh off a brand-new Percona branding reveal — complete with a rugged mountain goat mascot, a custom-made goat cake, and even a (surprisingly inspirational) LEGO mountain goat — we set out to bring our global Software Engineering team together in Turkey.&lt;/p>
&lt;p>And while planning an offsite for a fully remote, globally distributed team sounds simple enough… just wait until you try to do it.&lt;/p>
&lt;p>We’re talking about 100 people, 30+ countries, coordinating over 200,000 miles of travel, multiple time zones, passports, dietary restrictions, flight delays, and the occasional “wait, what do you mean my passport expires in three months?” &lt;em>(you know who you are)&lt;/em> moment. Suddenly, you’re not just planning a trip — you’re orchestrating a small traveling universe.&lt;/p>
&lt;p>Welcome to the step-by-step story of how we brought a global Percona team together in Turkey, and what it &lt;em>really&lt;/em> takes to make something like this happen.&lt;/p>
&lt;h2 id="step-one-accept-that-details-are-your-new-personality">Step One: Accept That Details Are Your New Personality&lt;/h2>
&lt;p>When planning an offsite of this scale, you quickly learn one thing: there is no such thing as being “too detailed.”&lt;/p>
&lt;p>There is also no such thing as too much information, or having too much collaboration, when it comes to working through the countless moving pieces.&lt;/p>
&lt;p>Success at an event like this doesn’t come from starting from scratch. It comes from leaning heavily on the people who have done it before. Their experiences, lessons learned, and even their past mistakes are incredibly valuable.&lt;/p>
&lt;p>We needed to actively seek out those insights, ask questions, and build on what already existed rather than reinventing the wheel. If previous events weren’t successful, you probably wouldn’t be doing it again. Lean into previous experiences and successes.&lt;/p>
&lt;p>At the same time, it’s &lt;strong>not just about repeating what’s been done — it’s about refining it.&lt;/strong> Each offsite should be a better, more thoughtful version of the last. That means taking previous experiences, identifying what worked and what didn’t, and making deliberate improvements across logistics, communication, and attendee experience.&lt;/p>
&lt;p>None of this happens last minute. To truly manage expectations and execute smoothly, planning conversations need to start early — ideally 6–8 months in advance &lt;em>(even though the Percona Turkey retreat was accomplished in just four months)&lt;/em>. That runway gives teams the time to align on goals, pressure-test ideas, collaborate across functions, and work through the inevitable complexities that come with an event of this scale.&lt;/p>
&lt;p>Success lies in thinking through &lt;em>every single touchpoint&lt;/em> of the attendee experience. From the moment attendees board their flight, train, car, or motorcycle to the final farewell high-five, it’s an experience.&lt;/p>
&lt;p>Paying close attention to each touchpoint shows up in many moments, including:&lt;/p>
&lt;ul>
&lt;li>Branded coasters placed on every table setting during business meetings (yes, people notice)&lt;/li>
&lt;li>Signage throughout the venue so no one ends up in a yoga class instead of a database workshop&lt;/li>
&lt;li>Check-in gifts that reinforce the Percona brand (and maybe include a surprise or two… goat-related, naturally)&lt;/li>
&lt;li>Carefully planned cocktail hours where “networking” magically turns into genuine connection&lt;/li>
&lt;li>Group dinners where teammates finally meet the humans behind the Slack avatars&lt;/li>
&lt;li>A candy swap featuring treats from every country represented — it doesn’t need to be formal. Everyone piles their local candies on a table for the masses&lt;/li>
&lt;li>A myriad of team-building activities spanning four days, including a competition to build the tallest free-standing printer-paper tower using only paper and tape &lt;em>(sidenote: be prepared for every single team to think that they won and the other teams cheated)&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1500" height="1999" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/turkey-paper-tower_hu_78dbbecdb0b971f.webp 768w, https://percona.community/blog/2026/08/turkey-paper-tower_hu_be7d05635188ac5a.webp 480w, https://percona.community/blog/2026/08/turkey-paper-tower_hu_cb75a0339142088b.webp 1400w" src="https://percona.community/blog/2026/08/turkey-paper-tower_hu_cb75a0339142088b.webp" alt="Team building a paper tower at the Percona Turkey offsite" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>But what people remember most isn’t just the agenda, t-shirts, stickers, goat swag, candy, or coffee breaks. It’s the combination of these thoughtful touches woven into every moment.&lt;/p>
&lt;p>It’s not just logistics that make the trip — it’s using those details and that collaboration to master storytelling.&lt;/p>
&lt;h2 id="step-two-time-may-not-be-on-your-side-but-deadlines-and-effective-communication-are">Step Two: Time May Not Be on Your Side (But Deadlines and Effective Communication Are)&lt;/h2>
&lt;p>A trip like this doesn’t come together overnight. But sometimes it needs to come together pretty close to overnight. That means a very well thought-out internal communications plan and schedule that answers as many questions as possible &lt;em>before&lt;/em> they are even asked.&lt;/p>
&lt;p>But no matter how solid a formal communications plan is, there will be moments when questions get asked that were already addressed — which is why finding other ways to communicate, collaborate, and involve stakeholders and attendees is &lt;em>crucial&lt;/em>. Think dedicated Slack channels with full transparency and readily available information, weekly touchpoints with department heads who can convey updates and reminders to their teams, and frequent conversations with finance, human resources, and legal throughout.&lt;/p>
&lt;p>Behind the scenes, you’re looking at &lt;strong>100–150 hours of internal planning&lt;/strong> — all before the first suitcase is packed.&lt;/p>
&lt;p>With that level of complexity, time becomes both a constraint and a forcing function. Deadlines matter, timelines matter, and how you communicate along the way matters even more. Clear, consistent communication is what keeps everything moving forward when there are dozens of parallel workstreams and stakeholders involved.&lt;/p>
&lt;p>Internally, communication has to be frequent, detailed, and often repetitive. There’s no room for assumptions or “I thought someone else had that covered.” Every update, decision, dependency, and small detail needs to be shared openly and documented so nothing slips through the cracks.&lt;/p>
&lt;p>The more visibility the team has, the more aligned everyone stays.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="627" height="836" sizes="(max-width: 627px) 100vw, 627px" srcset="https://percona.community/blog/2026/08/turkey-dinner-toast_hu_54f982a0f3d51273.webp 480w, https://percona.community/blog/2026/08/turkey-dinner-toast_hu_b32ea8eaac324c3b.webp 627w" src="https://percona.community/blog/2026/08/turkey-dinner-toast_hu_b32ea8eaac324c3b.webp" alt="Colleagues toasting at dinner during the Turkey offsite" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>That means being proactive, not reactive. Attendees need consistent touchpoints leading up to the trip: detailed itineraries, travel guidance, visa reminders, packing expectations, local logistics, and clear points of contact. Information should be centralized, easy to access, and reinforced multiple times so no one is left guessing.&lt;/p>
&lt;p>During the event, communication doesn’t stall. In fact, it accelerates — with adaptability, pivots, and clear, concise explanations.&lt;/p>
&lt;p>Real-time updates, schedule reminders, transportation details, and contingency plans all need to be communicated clearly and quickly. When done right, attendees feel taken care of, confident, and fully present in the experience rather than worrying about logistics.&lt;/p>
&lt;p>At the end of the day, strong communication — both internally and externally — is what turns a complex, global operation into something that feels seamless. It’s not just about sharing information; it’s about creating clarity, building trust, and ensuring every single person knows exactly where they need to be and when.&lt;/p>
&lt;p>This also means over-communicating by design. Weekly check-ins turn into twice-weekly syncs as the event gets closer. Quick updates become detailed run-of-show documents. Slack threads involving all attendees, shared docs, and status trackers become the backbone of execution. It may feel like a lot, but that level of transparency is what prevents last-minute surprises.&lt;/p>
&lt;p>Oh yeah — then there’s vendor coordination and communication.&lt;/p>
&lt;p>Hotels. Transportation. AV teams. Catering. Swag production. Signage. Shipping. Local experiences. Backup plans for your backup plans.&lt;/p>
&lt;p>Each one comes with:&lt;/p>
&lt;ul>
&lt;li>Deadlines&lt;/li>
&lt;li>Dependencies&lt;/li>
&lt;li>Payment schedules&lt;/li>
&lt;li>And the occasional “we need final numbers by tomorrow or your menu disappears” situation&lt;/li>
&lt;/ul>
&lt;p>Miss a deadline? That’s not just a small hiccup — it can mean:&lt;/p>
&lt;ul>
&lt;li>Increased costs&lt;/li>
&lt;li>Limited availability&lt;/li>
&lt;li>Or a last-minute scramble that no one wants to experience&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Precision, effective communication, and timing aren’t optional — they’re everything.&lt;/strong>&lt;/p>
&lt;h2 id="step-three-build-the-experience-not-just-the-agenda">Step Three: Build the Experience, Not Just the Agenda&lt;/h2>
&lt;p>You can have the best presentations in the world, but that’s not what people will remember.&lt;/p>
&lt;p>What they &lt;em>will&lt;/em> remember — and what they will certainly talk about afterwards:&lt;/p>
&lt;ul>
&lt;li>The first time they met a teammate in person after years of working together online&lt;/li>
&lt;li>The laughs over dinner&lt;/li>
&lt;li>The spontaneous conversations during coffee breaks&lt;/li>
&lt;li>The shared “we made it here” energy&lt;/li>
&lt;/ul>
&lt;p>For a company like Percona — &lt;strong>100% remote, spanning 50+ countries&lt;/strong> — these experiences and moments aren’t just nice-to-haves. They’re essential.&lt;/p>
&lt;p>An offsite like this creates:&lt;/p>
&lt;ul>
&lt;li>Stronger team bonding across regions and roles&lt;/li>
&lt;li>Real human connections that make collaboration smoother&lt;/li>
&lt;li>Cross-functional understanding that Slack threads simply can’t replicate&lt;/li>
&lt;li>A sense of belonging that transcends time zones&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1999" height="1502" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/turkey-card-game_hu_82ced5da6caca813.webp 768w, https://percona.community/blog/2026/08/turkey-card-game_hu_76585ffa1978d3f9.webp 480w, https://percona.community/blog/2026/08/turkey-card-game_hu_d27a564327e7cdbe.webp 1400w" src="https://percona.community/blog/2026/08/turkey-card-game_hu_d27a564327e7cdbe.webp" alt="Colleagues playing cards together at the Turkey offsite" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In short: it turns coworkers into teammates. As a remote-only company, Percona has a uniqueness with nearly 14% of employees who have been with the company for 10+ years. This isn’t an accident — it’s a direct result of the kind of networking, partnership, and friendships that these types of retreats create.&lt;/p>
&lt;p>The agenda for an offsite retreat should be intentionally designed to educate, inspire, and elevate the overall experience — not just fill time on a schedule. Every presentation needs to be thoughtfully curated to add real value, ensuring it contributes meaningfully rather than feeling like content for content’s sake.&lt;/p>
&lt;h2 id="step-four-expect-the-unexpected-and-pack-snacks">Step Four: Expect the Unexpected (and Pack Snacks)&lt;/h2>
&lt;p>No matter how well you plan, something will go sideways.&lt;/p>
&lt;p>Flights will be delayed. Luggage will take a scenic tour of another country. Someone will forget something important (realistically, multiple someones).&lt;/p>
&lt;p>The key is not avoiding problems, but trusting yourself, your team, and your planning to be ready for them.&lt;/p>
&lt;p>A few survival tips:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Always have contingency plans (plural). Seriously. Always. And seriously — plural.&lt;/strong> Hurricane hitting the beach on closing outdoor dinner night? Have a space ready to go and an action plan for communicating the pivot. All your swag held up in customs? Find the local flea market and craft shops and make your gifts local. Speaker forgot a slide advancer &lt;em>and&lt;/em> the venue’s failed &lt;em>and&lt;/em> the venue’s backup failed? Keep one on the ready with you at all times.&lt;/li>
&lt;li>&lt;strong>Over-communicate everything.&lt;/strong> You can’t tell people important things too many times.&lt;/li>
&lt;li>&lt;strong>Build buffer time into the schedule.&lt;/strong> Imagine trying to get your three-year-old out the door early in the morning. It’s like that, but with 100 adults. Give yourself some cushions.&lt;/li>
&lt;li>&lt;strong>Keep a sense of humor handy&lt;/strong> — it’s arguably your most valuable tool. You can diffuse a lot of tension with a little humorous quip. Humor brings the tension down, eases the panic, and grounds everyone. Even the most panic-stricken, reactionary executive you’ve ever met enjoys a good laugh in tense moments.&lt;/li>
&lt;li>&lt;strong>Slow down&lt;/strong> — panic happens when speed outmaneuvers thought. It’s not a race, and you don’t have to beat everyone to a solution. Trust yourself and trust your team, and slow down enough to think through clearly without the heaviness of undue pressure.&lt;/li>
&lt;/ul>
&lt;h2 id="step-five-remember-why-youre-doing-it">Step Five: Remember Why You’re Doing It&lt;/h2>
&lt;p>After the spreadsheets, the emails, the vendor calls, and the 47th revision of the rooming list, it’s easy to forget the bigger picture.&lt;/p>
&lt;p>But then the event starts.&lt;/p>
&lt;p>People arrive. Conversations spark. Teams connect. Ideas flow. Energy builds.&lt;/p>
&lt;p>And suddenly, all 100–150 hours (and then some) make perfect sense.&lt;/p>
&lt;p>What you’ve created isn’t just an offsite — it’s an experience that strengthens your team in ways no virtual meeting ever could.&lt;/p>
&lt;p>Don’t lose sight of your objective.&lt;/p>
&lt;h2 id="final-thoughts-keep-climbing">Final Thoughts: Keep Climbing&lt;/h2>
&lt;p>Bringing 100 people across the world to Turkey wasn’t easy — but it was worth every detail, every deadline, and every late-night planning session.&lt;/p>
&lt;p>When you return home from executing a trip like this, you will want to shut off all notifications and take a few days to yourself without having to resolve anything. But also make sure you ponder what you would have liked to see done differently. Would arranging transfers from the airport to the resort be more cost-effective and smooth than relying on taxis? Most likely. Would building in more leisure time and opportunities for attendees to explore the local surroundings be worth sacrificing one or two sessions of training? Absolutely.&lt;/p>
&lt;p>Learn from that and adapt for next time.&lt;/p>
&lt;p>Because at the end of the day, investing in your people — especially in a remote-first world — is one of the most impactful things you can do.&lt;/p>
&lt;p>And if that investment comes with a mountain goat mascot, a LEGO companion, custom cakes, coasters, t-shirts, drinks, food, and out-of-shape pickup basketball games, then you will find a team that fully leans into the journey and the message.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="836" height="627" sizes="(max-width: 836px) 100vw, 836px" srcset="https://percona.community/blog/2026/08/turkey-road-trip_hu_7b9668a0c74d8da9.webp 768w, https://percona.community/blog/2026/08/turkey-road-trip_hu_361debda08ebab1.webp 480w, https://percona.community/blog/2026/08/turkey-road-trip_hu_29b42cc1383d4ef.webp 836w" src="https://percona.community/blog/2026/08/turkey-road-trip_hu_29b42cc1383d4ef.webp" alt="Two Perconians on a post-retreat road trip in Turkey" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We came as individuals from across the globe.&lt;/p>
&lt;p>On the day the retreat ended, I found myself in a rental car with a colleague I’d never met in person before. Two co-workers from different sides of the globe, different cultures, and two very different paths that led us to that spot. We drove for a full day, exploring site after site and chatting and laughing about life, work, and music.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1500" height="1999" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/08/turkey-team-photo_hu_61f2e6a7cafb476c.webp 768w, https://percona.community/blog/2026/08/turkey-team-photo_hu_bed32c06044db31c.webp 480w, https://percona.community/blog/2026/08/turkey-team-photo_hu_e6a2b147056b27d.webp 1400w" src="https://percona.community/blog/2026/08/turkey-team-photo_hu_e6a2b147056b27d.webp" alt="The Software Engineering team at the Percona Turkey offsite" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This interaction and moment doesn’t happen without retreats like this.&lt;/p>
&lt;p>We came together as members of the Software Engineering group at a place we all worked, and left as a unified team of Perconians — and as friends.&lt;/p>
&lt;p>&lt;strong>That’s your &lt;em>real&lt;/em> ROI.&lt;/strong>&lt;/p></content:encoded><author>Kyle Flanagan</author><category>Community</category><category>Percona</category><category>Events</category><category>Remote Work</category><category>Team</category><media:thumbnail url="https://percona.community/blog/2026/08/turkey-road-trip-cover_hu_892a71fad2f5929a.jpg"/><media:content url="https://percona.community/blog/2026/08/turkey-road-trip-cover_hu_a75d3a0c80fc7206.jpg" medium="image"/></item><item><title>Introducing 🍃MClusterAdmin: A Lightweight GUI Tool for MongoDB DBAs</title><link>https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/</link><guid>https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/</guid><pubDate>Mon, 27 Jul 2026 00:00:00 UTC</pubDate><description>Over the years working on various MongoDB troubleshooting cases, I’ve been wondering how to handle the longish JSON outputs from the most common diagnostic commands, like rs.status(), sh.status(), or db.currentOp(), not to mention db.serverStatus()! They are just painful and slow to read. I even came up with various scripts to present the data in table format, similar to what we know from MySQL, but I was never satisfied with them.</description><content:encoded>&lt;p>Over the years working on various MongoDB troubleshooting cases, I’ve been wondering how to handle the longish JSON outputs from the most common diagnostic commands, like &lt;code>rs.status()&lt;/code>, &lt;code>sh.status()&lt;/code>, or &lt;code>db.currentOp()&lt;/code>, not to mention &lt;code>db.serverStatus()&lt;/code>! They are just painful and slow to read. I even came up with various scripts to present the data in &lt;a href="https://github.com/kellyjonbrazil/jtbl" target="_blank" rel="noopener noreferrer">table format&lt;/a>, similar to what we know from MySQL, but I was never satisfied with them.&lt;/p>
&lt;p>Finally, I thought it was futile to look for a universal solution that would yield human-friendly outputs in command-line sessions.&lt;/p>
&lt;p>So, why not have a nice graphical interface to visualize replica sets, sharding state, connections, and more instead? I looked for available GUI tools for MongoDB and came to the impression that &lt;strong>almost all of them are built&lt;/strong> &lt;strong>for developers&lt;/strong>. They are great for browsing collections, editing documents, or composing queries, but when it comes to typical &lt;strong>DBA&lt;/strong> daily tasks — checking replication health, inspecting replica set configuration, or understanding what is really going on inside a sharded cluster — we are really down to the mongo shell. There is nothing wrong with the shell client, of course, but some things are simply easier to digest when presented visually. Here, of course, &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/install-pmm/install-pmm-client/connect-database/mongodb.html" target="_blank" rel="noopener noreferrer">PMM&lt;/a> does allow that, but it has a bit of a different purpose - long-term monitoring, and has to be installed and set up first. Besides, it does not do everything I wanted.&lt;/p>
&lt;p>So, encouraged to experiment with vibe coding in Percona, I decided to experiment with filling this gap myself and started a small personal project: a lightweight &lt;strong>DBA-oriented&lt;/strong> GUI interface called &lt;strong>MClusterAdmin&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="662" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/mclusteradmin-picture_hu_bc79c6f241007840.webp 768w, https://percona.community/blog/2026/07/mclusteradmin-picture_hu_1cb0840b71b519.webp 480w, https://percona.community/blog/2026/07/mclusteradmin-picture_hu_cc2926011e71b971.webp 1400w" src="https://percona.community/blog/2026/07/mclusteradmin-picture_hu_cc2926011e71b971.webp" alt="MClusterAdmin" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="project-description">Project description&lt;/h3>
&lt;p>MClusterAdmin is a tool designed to be self-hosted, with a backend written in Go, while the frontend is in HTML and JavaScript. It offers a web UI interface featuring typical &lt;strong>DBA perspective&lt;/strong> dashboards. The whole application ships as a single, small binary, with &lt;strong>no agents&lt;/strong>, no external services, and no internal database of its own.&lt;/p>
&lt;p>In order to try it, you need to copy the binary to a host that has access to the MongoDB servers to be monitored. I suggest running first locally on some test environment, like one created using &lt;a href="https://github.com/PrzemekMalkowski/mlaunch-go" target="_blank" rel="noopener noreferrer">mlaunch&lt;/a>, &lt;a href="https://github.com/zelmario/anydbver" target="_blank" rel="noopener noreferrer">anydbver&lt;/a>, &lt;a href="https://github.com/percona/mongo_terraform_ansible" target="_blank" rel="noopener noreferrer">mongo_terraform_ansible&lt;/a>, or a similar sandbox tool. For &lt;strong>Docker&lt;/strong> environments, you can use the already existing &lt;a href="https://github.com/PrzemekMalkowski/mclusteradmin/pkgs/container/mclusteradmin" target="_blank" rel="noopener noreferrer">image&lt;/a>, just make sure your container shares the same network as the MongoDB cluster.&lt;/p>
&lt;p>The tool has no authentication on its own. Very similarly to mongosh, the MongoDB URI and credentials you provide determine what it will be able to offer. You can connect a standalone instance, a replica set member, or a mongos router. The tool will discover the rest of the topology automatically and will establish individual connections to each member. No SSH access to the database hosts is needed.&lt;/p>
&lt;p>With its tiny footprint, it should fit well into your cloud/K8S MongoDB deployments.&lt;/p>
&lt;p>Let me be clear about what MClusterAdmin is &lt;strong>not&lt;/strong>: it is not a data browser — you will not view or edit your collections’ documents with it. There are plenty of other tools for that! It is, again, not a long-term monitoring or alerting solution; for that, I strongly recommend &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a>. MClusterAdmin is designed for quick, interactive cluster inspection and simple administrative operations that a DBA performs many times a day.&lt;/p>
&lt;p>I will point out the key features that I decided to implement further below, but I guess watching a quick &lt;strong>demo presentation&lt;/strong> will allow you to judge the tool much faster:&lt;/p>
&lt;div class="youtube__block">&lt;iframe src="https://www.youtube.com/embed/RecQ7wtEV9g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>&lt;/iframe>&lt;p>Link: &lt;a href="https://youtu.be/RecQ7wtEV9g" class="youtube__link" target="_blank">https://youtu.be/RecQ7wtEV9g&lt;/a>&lt;/p>&lt;/div>
&lt;p>I don’t think it makes sense to describe all the features I was able to implement so far in detail. I hope everything is intuitive enough so that you can see for yourself. The &lt;a href="https://github.com/PrzemekMalkowski/mclusteradmin/blob/master/README.md" target="_blank" rel="noopener noreferrer">documentation&lt;/a> is available on GitHub if needed.&lt;/p>
&lt;p>In short, my aim was to provide useful views covering &lt;strong>replication topology&lt;/strong> and settings, sharding members and routers, but also overall database size statistics and their distribution among shards. This includes per-collection detailed sharding stats, data, and index sizes, etc. In addition to that, for each discovered MongoDB instance, you should be able to see basic WiredTiger usage stats, oplog details, including an on-demand breakdown of &lt;strong>which collections changes generated the most recent oplog traffic&lt;/strong>, as well as some other most important server settings and usage summaries.&lt;/p>
&lt;p>You will also find slow queries profiling, with the query explain module, as well as a quite functional users and roles management section.&lt;/p>
&lt;p>The last &lt;strong>security-focused&lt;/strong> section allows you to check whether authentication is enabled on each host, as well as what is the usage of each authentication mechanism.&lt;/p>
&lt;h3 id="getting-started">Getting Started&lt;/h3>
&lt;p>Running MClusterAdmin takes a few seconds. Just download the suitable binary from the &lt;a href="https://github.com/PrzemekMalkowski/mclusteradmin/releases" target="_blank" rel="noopener noreferrer">release page&lt;/a>, or compile it yourself:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone https://github.com/PrzemekMalkowski/mclusteradmin.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> mclusteradmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go build -o mca .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./mca&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then open the relevant address (by default http://localhost:8787) in your browser and paste your MongoDB connection URI to connect. There is also a &lt;code>--view-only&lt;/code> flag, which disables all mutating operations both in the UI and at the API level — handy when you just want a safe, read-only window into a cluster. TLS mode for the web service interface is supported as well.&lt;/p>
&lt;h3 id="kubernetes-integration">Kubernetes integration&lt;/h3>
&lt;p>It is extremely easy to add MClusterAdmin as a diagnostic pod to your existing MongoDB cluster in Kubernetes. Just use the available &lt;a href="https://github.com/PrzemekMalkowski/mclusteradmin/pkgs/container/mclusteradmin" target="_blank" rel="noopener noreferrer">Docker image&lt;/a> from the GitHub repository!&lt;/p>
&lt;p>An example MClusterAdmin.yaml configuration can be as simple as:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: apps/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Deployment
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: MClusterAdmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selector:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> matchLabels:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app: MClusterAdmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> template:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> labels:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app: MClusterAdmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> containers:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: MClusterAdmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: ghcr.io/przemekmalkowski/mclusteradmin:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - containerPort: 8787&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="a-word-of-caution">A Word of Caution&lt;/h3>
&lt;p>MClusterAdmin is currently in &lt;strong>beta&lt;/strong>, and it is a personal side project, so please do not point it at your production clusters just yet — test it in a safe environment first. The connected MongoDB user needs privileges matching the dashboards you want to use; the documentation breaks these down per feature, so you can follow the least-privilege approach instead of simply granting root.&lt;/p>
&lt;h3 id="summary">Summary&lt;/h3>
&lt;p>MClusterAdmin was created as an attempt to fill the gap I found to be the case in the MongoDB community: a lightweight, replication- and sharding-aware GUI for MongoDB &lt;strong>DBAs&lt;/strong>, free from any data-browsing ballast. The project is open source (GPLv3), and the code is available on &lt;a href="https://github.com/PrzemekMalkowski/mclusteradmin" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>. If you find it useful, miss a feature, or hit a bug, I would love to hear from you. Feedback is very welcome!&lt;/p>
&lt;p>&lt;em>The article was created by a human.&lt;/em>&lt;/p></content:encoded><author>Przemek Malkowski</author><category>MongoDB</category><category>Opensource</category><category>DBA</category><category>GUI</category><category>Visibility</category><media:thumbnail url="https://percona.community/blog/2026/07/mclusteradmin_hu_aba2ed076fe46238.jpg"/><media:content url="https://percona.community/blog/2026/07/mclusteradmin_hu_ace1efd6b3e531f0.jpg" medium="image"/></item><item><title>TDE performance in PostgreSQL</title><link>https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/</link><guid>https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/</guid><pubDate>Mon, 20 Jul 2026 00:00:00 UTC</pubDate><description>What’s the impact of TDE on performance? People usually quickly throw together a few graphs with basic measurements and treat that as a complete answer, but the question is a bit more complex than that. In this blog post, I’ll try to explain it in a bit more detail: why showcasing a single graph isn’t good for anything other than marketing.</description><content:encoded>&lt;p>What’s the impact of TDE on performance?
People usually quickly throw together a few graphs with basic measurements and treat that as a complete answer, but the question is a bit more complex than that.
In this blog post, I’ll try to explain it in a bit more detail: why showcasing a single graph isn’t good for anything other than marketing.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1672" height="941" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/pg_tde_superfast_hu_724b2cb64baf2a37.webp 768w, https://percona.community/blog/2026/07/pg_tde_superfast_hu_558fc7ca98fef308.webp 480w, https://percona.community/blog/2026/07/pg_tde_superfast_hu_c87d7fd4b0b5c46a.webp 1400w" src="https://percona.community/blog/2026/07/pg_tde_superfast_hu_c87d7fd4b0b5c46a.webp" alt="It’s SUPER FAST!" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="agenda">Agenda&lt;/h2>
&lt;p>Let me start by making something clear: this is a complex topic, and this will be a long blog post.&lt;/p>
&lt;p>I decided to simplify several things to the level where it is accurate enough, but still relatively easy to understand.
I also won’t go into implementation details, the maths behind statistics, and I won’t focus on generic topics like how to do low-noise benchmarking.&lt;/p>
&lt;p>I am already planning on writing separate blog posts about some of these details in the future, but if you are interested in some of them, don’t hesitate to ask!
Feedback like that helps me figure out what topic to cover next.&lt;/p>
&lt;p>As for this blog post, I want to cover the following topics:&lt;/p>
&lt;ul>
&lt;li>A generic introduction about the cost of encryption on modern CPUs&lt;/li>
&lt;li>A short description of how transparent data-at-rest encryption typically integrates into PostgreSQL&lt;/li>
&lt;li>An explanation of why relation encryption (encrypting the database objects) typically has no cost at all in most workloads, except a few specific operations&lt;/li>
&lt;li>And finally showcasing how the typical way of implementing WAL encryption in TDE solutions can cause performance degradation in high WAL-churn scenarios&lt;/li>
&lt;/ul>
&lt;h2 id="test-setup">Test setup&lt;/h2>
&lt;p>Benchmarks heavily depend on the computer used for running them.
All of my tests were performed on an AMD Threadripper 3970X (32 cores), using an Intel Optane P5800X SSD.&lt;/p>
&lt;p>While some of the measurements can be reproduced on typical desktop hardware, not all of them can.
Some tests require many parallel workers and high memory bandwidth.
The more interesting tests all measure write performance, which is difficult on typical M.2 SSDs:
while some of them have peak write performance similar to Optane disks, they can only keep up with high write speeds for short bursts, quickly turning an otherwise CPU or memory limited test into an IO limited one.&lt;/p>
&lt;h2 id="encryption-is-cheap-in-isolation">Encryption is cheap… in isolation&lt;/h2>
&lt;p>When talking about encryption performance, technical people usually make one of two assumptions:&lt;/p>
&lt;ul>
&lt;li>Encryption is complex math we have to compute, so of course it will degrade our performance!&lt;/li>
&lt;li>We are using things like full filesystem encryption (BitLocker, LUKS), swap encryption or TLS all the time, it’s barely noticeable – encryption is cheap!&lt;/li>
&lt;/ul>
&lt;p>And both groups are kind of right:
Encryption is complex math, and it will degrade performance on older hardware.
But because it is so common and required for everything, modern hardware has a specialized instruction set (AES-NI) that highly optimizes it for most workloads.&lt;/p>
&lt;p>The following table shows some single threaded measurements on my test computer, produced with small C benchmarks:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>What&lt;/th>
&lt;th>Bandwidth (GB/s)&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Memory read&lt;/td>
&lt;td>23&lt;/td>
&lt;td>How quickly can we read memory&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Memory copy&lt;/td>
&lt;td>11&lt;/td>
&lt;td>How quickly can we copy data in memory from one place to another&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Disk sequential read&lt;/td>
&lt;td>7.4&lt;/td>
&lt;td>How quickly can we read from disk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Disk sequential write&lt;/td>
&lt;td>6.1&lt;/td>
&lt;td>How quickly can we write to disk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-128-CTR&lt;/td>
&lt;td>8.0&lt;/td>
&lt;td>How quickly can we perform 128 bit AES CTR operations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-256-CTR&lt;/td>
&lt;td>6.5&lt;/td>
&lt;td>How quickly can we perform 256 bit AES CTR operations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-128-XTS&lt;/td>
&lt;td>7.3&lt;/td>
&lt;td>How quickly can we perform 128 bit AES XTS operations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-256-XTS&lt;/td>
&lt;td>5.6&lt;/td>
&lt;td>How quickly can we perform 256 bit AES XTS operations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-128-GCM&lt;/td>
&lt;td>3.8&lt;/td>
&lt;td>How quickly can we perform 128 bit AES GCM operations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AES-256-GCM&lt;/td>
&lt;td>3.6&lt;/td>
&lt;td>How quickly can we perform 256 bit AES GCM operations&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The three encryption modes mentioned above are commonly used in many scenarios:&lt;/p>
&lt;ul>
&lt;li>XTS is commonly used for full disk encryption, and also by some TDE implementations&lt;/li>
&lt;li>CTR is commonly used by TDE implementations, especially for WAL encryption&lt;/li>
&lt;li>GCM is an authenticated encryption algorithm which can provide both encryption and data integrity validation for TDE and other solutions&lt;/li>
&lt;/ul>
&lt;p>While the table doesn’t mention it, I also want to point out that all reads and writes are sequential using a 4kB block size.
This is important, because the performance of all of them degrades if we start using them differently: if we start encrypting much smaller blocks at a time, or keep reinitializing the stream with different parameters, the encryption numbers can degrade quickly.&lt;/p>
&lt;p>We also have to remember that our goal isn’t to encrypt a random stream in memory – we have to integrate encryption into an existing database system with its own established architecture.
The above numbers showcase our bandwidth to encrypt or decrypt data if a CPU core is only working on that task.
In reality, the CPU will also be doing other things at the same time, and can’t spend all the time on encryption.&lt;/p>
&lt;p>However, this won’t necessarily make things worse.
Most database workloads are not CPU bound, the bottleneck is usually either disk or memory bandwidth.
With AES-NI, encryption operations often don’t require additional memory bandwidth, which means that if a workload is already disk or memory limited, but we have free CPU cycles, we might get encryption for free or at little cost.&lt;/p>
&lt;p>The real question isn’t how quick encryption is, but how optimally we can integrate it into PostgreSQL.&lt;/p>
&lt;h2 id="what-are-we-encrypting-exactly">What are we encrypting exactly?&lt;/h2>
&lt;p>That means we no longer have a single question.
I can’t give a single answer to &lt;em>how fast is pg_tde, or any other data-at-rest encryption implementation?&lt;/em>&lt;/p>
&lt;p>Because a database does many things, reads and writes many different file types, and each of those has to be implemented differently.&lt;/p>
&lt;p>In the case of pg_tde, we have two main areas:&lt;/p>
&lt;ul>
&lt;li>the encryption of database (relation) files&lt;/li>
&lt;li>and the encryption of the write ahead log&lt;/li>
&lt;/ul>
&lt;p>Other TDE implementations might encrypt other files too, for example temporary files, but I want to focus on the two areas supported by pg_tde, as these are the most significant from a performance perspective.&lt;/p>
&lt;p>So let’s look into the details of these separately.&lt;/p>
&lt;h2 id="the-relation-files">The relation files&lt;/h2>
&lt;p>The quick summary for those only interested in the numbers:
the impact for this is very little – for most operations, in the very difficult to measure category.
The only exception to this is a few single threaded write heavy workloads, such as &lt;code>CREATE TABLE AS SELECT ...&lt;/code>, &lt;code>VACUUM FULL&lt;/code>, an &lt;code>UPDATE&lt;/code> that updates all or most rows, or an &lt;code>ALTER TABLE&lt;/code> that rewrites the entire table. In these, we can measure a 5-30% performance drop.&lt;/p>
&lt;p>For other operations, such as typical sysbench workloads, or even specific tests like single worker sequential reads, that number is 2% or less.
Typical measurements usually have some noise, a few percent even for properly configured setups, and much more for &lt;em>“let’s just quickly execute sysbench”&lt;/em>.
Something in the 1-2% range can only be measured with specific server and hardware configuration, not in real-world setups.&lt;/p>
&lt;p>The reason behind this is quite simple:
Relation file reads and writes usually happen in entire blocks, with an 8k default size for PostgreSQL – it is very similar to OS level file system encryption.&lt;/p>
&lt;p>A typical data-at-rest encryption implementation usually operates at the IO level: it encrypts immediately before we write a dirty buffer to disk, and it decrypts immediately after we read something from disk.&lt;/p>
&lt;p>Decryption only happens when we actually have to read from disk. If the requested pages are already in the shared buffers, they are already decrypted, so there’s no effect there.
Encryption only happens when we are writing to disk. In a workload that isn’t very write-heavy, all writes happen in the checkpointer and background writer, and backend processes never perform page writes directly, so encryption won’t affect the QPS numbers at all.
Even if a workload is so write-heavy that the server has to move some of the writes to the backend process, most likely the backend isn’t CPU-bound, since most database workloads are either memory or IO limited.
Unless we are writing a huge volume of data, such as the examples mentioned above, encryption won’t be noticeable at all in these scenarios.&lt;/p>
&lt;p>This read-write behavior is similar to full file system encryption, where the OS caches behave similarly, unless we keep calling &lt;code>fsync&lt;/code> explicitly for writes.&lt;/p>
&lt;p>Because the effect of encryption is so little, we have to be really careful how we set up our test environment, even for the heavy write scenarios where we can notice a somewhat larger difference.
It’s very easy to get this wrong, and then just measure noise.&lt;/p>
&lt;p>For example, let’s say that we don’t want to spend too much time on initializing the dataset, and we only generate a 20GB dataset.
We also set the shared buffers to 16GB because our test PC has more than enough RAM.
This seems reasonable, but the problem is, once the data is loaded into the shared buffers, it will be permanently decrypted there, and 80% of our dataset fits into the buffers.
Most read operations won’t have to go to the disk, they find the requested page in the buffers, so we are not measuring encryption performance for them.&lt;/p>
&lt;p>We might decide to keep the small dataset, and just use a small number for shared buffers, but then we are moving away from a production-like setup: is it realistic to run PostgreSQL with 128MB shared buffers in a real environment?
Can the reduced shared buffer size affect the performance in some other ways?&lt;/p>
&lt;p>And then we have to think about similar questions for writes.&lt;/p>
&lt;p>Regardless of what we do, as I mentioned above, this is basically the same use case as file system encryption, and we already know that that’s fast.
If we are testing an encryption implementation, and we can see a significant performance degradation with only data file encryption, we found a bug, and should report it.&lt;/p>
&lt;h3 id="why-is-ctas-so-slow">Why is CTAS so slow?&lt;/h3>
&lt;p>At the beginning of the previous section, I mentioned a few examples where we &lt;em>can&lt;/em> measure a difference.&lt;/p>
&lt;p>In my tests, I see the following worst-case performance degradation with them:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Command&lt;/th>
&lt;th>Encryption Overhead&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>CREATE TABLE AS SELECT&lt;/code>&lt;/td>
&lt;td>30%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ALTER TABLE&lt;/code> performing a full rewrite&lt;/td>
&lt;td>15%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>VACUUM FULL&lt;/code>&lt;/td>
&lt;td>10%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>UPDATE&lt;/code> all rows&lt;/td>
&lt;td>10%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These are all &lt;em>worst case&lt;/em> numbers I was able to produce. This doesn’t mean that all full table UPDATE operations will take 10% longer.&lt;/p>
&lt;p>Also, in the case of &lt;code>UPDATE&lt;/code>, the 10% measurement includes the time of the &lt;code>CHECKPOINT&lt;/code>, as depending on the exact server configuration, some or most of the writes still happen in the checkpointer and background writer.
The actual execution time of the query in the backend process was the same with and without encryption in all of my &lt;code>UPDATE&lt;/code> measurements, unless I intentionally misconfigure the server.&lt;/p>
&lt;p>A CTAS that simply copies a table is the worst performer because it doesn’t have to do anything else.
The other operations all have to do something extra with the data, but if we just duplicate one database table, then we normally only perform IO:
read a page, write the page, repeat.&lt;/p>
&lt;p>Except in our case, we have to read a page, decrypt the page, encrypt the page, write the page.
We have to call encryption operations twice for every page: with AES-128-CTR at 8 GB/s, decrypting and then encrypting each page halves the effective bandwidth to around 4 GB/s, well below the disk speeds in my test setup.
A CTAS operation is normally IO limited, but because of this doubled encryption work, it becomes encryption limited with TDE, even with the AES-NI instruction set.&lt;/p>
&lt;p>I also want to explicitly repeat an important note from the beginning:
this result requires a sustained disk IO that is faster than the CPU’s encryption speed.
Consumer grade SSDs can’t sustain such speeds for long workloads, only for short bursts.
It is only possible to reproduce this measurement on them with small datasets.
With larger tables, the test will be limited by disk IO, not encryption.&lt;/p>
&lt;h2 id="the-write-ahead-log">The write ahead log&lt;/h2>
&lt;p>This is where things get more interesting.
In my &lt;a href="https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/">previous tde related blog post&lt;/a>, I mentioned that we are working on some encryption benchmarking, and that was about WAL performance.
Also, one of the main changes in the upcoming pg_tde 2.2.3 release will be a significant improvement in this area.&lt;/p>
&lt;p>To start with a similar summary:&lt;/p>
&lt;ul>
&lt;li>in my most-performant benchmark prototype, I can’t measure a significant degradation even in a &lt;strong>worst-case scenario workload&lt;/strong>&lt;/li>
&lt;li>with pg_tde before 2.2.3, the same test results in only 60% throughput, or a 40%+ performance degradation&lt;/li>
&lt;li>with pg_tde 2.2.3, we were able to improve that test to the 80% range, reducing the performance degradation to 20%&lt;/li>
&lt;/ul>
&lt;p>The 2.2.3 change (already merged, the release is upcoming) improves our current WAL encryption approach. The complete fix described later in this post is still in development.&lt;/p>
&lt;p>This list also needs two important footnotes:&lt;/p>
&lt;p>First, this is a “worst-case scenario”, not something typically executed in production workloads.
It is a synthetic scenario exactly to stress test WAL bottlenecks.
In a simple OLTP read-write scenario, there’s only a few percent measurable difference for all pg_tde versions, less than 10%.&lt;/p>
&lt;p>Second, this is a concurrency issue, and reproducing it requires a high core count test PC.
In my test setup, I have 32 cores available.
On different hardware, the results will be different.
With 100+ core monster hardware, and even more threads, the performance degradation is most likely even worse for most implementations of WAL encryption, but I didn’t perform tests on such setups.&lt;/p>
&lt;h3 id="understanding-the-issue">Understanding the issue&lt;/h3>
&lt;p>To understand why this is so different from data file encryption, we have to look into how WAL works:&lt;/p>
&lt;ol>
&lt;li>When we perform any WAL logged write, the changes are first written to WAL, that’s why it’s called a write &lt;strong>ahead&lt;/strong> log.&lt;/li>
&lt;li>Even more specifically, the backend process (the server’s handler for the specific client session) first constructs what it wants to write into the log&lt;/li>
&lt;li>After that, in PostgreSQL we only have a single WAL log, and only one backend can flush it at a time.
The backend process has to request a lock on the WAL, preventing other processes from interacting with it at the same time.&lt;/li>
&lt;li>After writing the already constructed data to the in-memory buffer, we have to write it to disk and then immediately flush the buffer to disk, to make sure that it is durable, since this is the log we are using for crash recovery.&lt;/li>
&lt;li>It can release that lock only after that write/flush was done.&lt;/li>
&lt;li>After releasing the lock, another backend can take it, repeating the process from (3)&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
sequenceDiagram
participant B1 as Backend 1
participant B2 as Backend 2
participant L as Single WAL lock
B1->>B1: construct WAL record
B2->>B2: construct WAL record
B1->>L: acquire
Note over B1,L: encrypt + write + flush&lt;br/>(everyone else waits)
B2--xL: blocked
L-->>B1: release
B2->>L: acquire
Note over B2,L: encrypt + write + flush
L-->>B2: release
&lt;/pre>
&lt;p>The above information is a bit oversimplified, as WAL writes are more complex than this, but it is already enough to spot the concurrency issue hidden in it:
only one process can write/flush the WAL at a time, so no matter how many cores we have, it won’t get faster.
In fact it is the opposite, since higher core count CPUs usually have worse single-thread performance.&lt;/p>
&lt;p>Most WAL encryption implementations, including pg_tde, implement it similarly to data file encryption:
we encrypt the WAL data immediately before writing it to disk, at the time when the backend already acquired the WAL lock.
Not only do we do additional computations for the current session, we also prevent other sessions from doing anything during this extra time.&lt;/p>
&lt;p>In my test setup with the above numbers, WAL writes were already CPU bound without encryption.
Even if encryption is relatively cheap, if we don’t have spare cycles, it will show up.
That’s bad enough already with a good implementation, but if we also manage to introduce a performance-hurting bug in this area of the code, we can easily end up with quite bad numbers.&lt;/p>
&lt;h3 id="is-this-completely-fixable">Is this completely fixable?&lt;/h3>
&lt;p>As I hinted at the beginning of the WAL section, this issue is fixable.
WAL encryption doesn’t inherently require encrypting more data than data file encryption does. In fact, if done correctly, most of the time we’ll have to encrypt even less.&lt;/p>
&lt;p>The problem is that we have to do it in a more challenging part of the code.&lt;/p>
&lt;p>From the above description, it might already be clear:
we should encrypt the data after we constructed it, before taking the WAL lock!&lt;/p>
&lt;p>&lt;strong>Current: encrypt inside the lock&lt;/strong>&lt;/p>
&lt;pre class="mermaid">
flowchart LR
A[construct record] --> B[acquire lock]
subgraph lock [lock held, serialized]
direction LR
C[encrypt] --> D[write + flush]
end
B --> C
D --> E[release lock]
&lt;/pre>
&lt;p>&lt;strong>Fixed: encrypt before the lock&lt;/strong>&lt;/p>
&lt;pre class="mermaid">
flowchart LR
A[construct record] --> C[encrypt]
C --> B[acquire lock]
subgraph lock [lock held, serialized and shorter]
direction LR
D[write + flush]
end
B --> D
D --> E[release lock]
&lt;/pre>
&lt;p>That solves both the concurrency limitation and another problem hidden there:
imagine that we are writing many small records, one per transaction.
A single WAL record might be less than 100 bytes, but a WAL page is 8kB.&lt;/p>
&lt;p>Even if we do it at flush time, we don’t have to encrypt the entire page, only what we filled so far, but on average even that results in 4kB data per flush.
With a 100-byte WAL record, we can fit around 80 records into a single WAL page.
We have written 8kB of real data, and encrypted 320kB of WAL to do it.
There are of course some possible optimizations there, for example we completely ignored group commit, which will likely make that 320kB number much smaller, but it still remains significantly more.&lt;/p>
&lt;p>While moving the encryption before the lock might seem like an easier choice, it has different challenges.
For example, there’s one I already mentioned in the beginning: if we start encrypting small blocks (and also changing the stream configuration, which is also related to this), we get worse encryption performance.&lt;/p>
&lt;p>The naive implementation of this approach performs even worse than our earlier pg_tde implementation.
However, if we apply some optimizations to it, it can reach similar “unmeasurable” levels as the data page encryption.&lt;/p>
&lt;p>This improvement is not yet included in pg_tde, as it requires a completely different approach to WAL encryption compared to our previous implementations, and we haven’t yet finished testing and measuring it.
Mainly, in this blog post I only focused on the performance of &lt;em>writing&lt;/em> the WAL, but we have to remember that we also have to &lt;em>read&lt;/em> it in some cases.
While the speed of crash recovery isn’t that important in a happy scenario, when something bad happens it does matter how quickly we can get our server running again.&lt;/p>
&lt;h3 id="whats-the-test-scenario">What’s the test scenario?&lt;/h3>
&lt;p>Similarly to the write bottlenecks above, a normal OLTP read-write workload won’t showcase significant degradation because of WAL encryption.
This is exactly why I wrote such a long post: the interesting behavior only shows up in scenarios a quick benchmark never exercises.&lt;/p>
&lt;p>We could simply publish a nice graph showing the typical numbers, claiming that our encryption is fast.
We could treat the performance problem we fixed as a small footnote in our changelog, as it doesn’t show up at all in that measurement.&lt;/p>
&lt;p>But that wouldn’t be honest, because the test setup and test scenarios do matter, and when we talk about performance, we should mention worst-case numbers.&lt;/p>
&lt;p>To reach these bad numbers, we have to do one thing:
generate a huge amount of WAL across many sessions.
This is doable in many different ways, ours is basically the following:&lt;/p>
&lt;ol>
&lt;li>create a few tables, each with a few million rows&lt;/li>
&lt;li>start executing &lt;code>UPDATE t SET c=c+1 WHERE id IN (SELECT id FROM t ORDER BY random() LIMIT 100);&lt;/code>&lt;/li>
&lt;li>run step 2 in many sessions&lt;/li>
&lt;/ol>
&lt;p>The query above is a simplified illustration. The actual test used sequential IDs, with the 100 random IDs generated by the benchmark tool and inserted directly into a prepared statement, so selecting the rows stays cheap and the workload is dominated by the WAL writes.&lt;/p>
&lt;p>PostgreSQL has an option called &lt;a href="https://wiki.postgresql.org/wiki/Full_page_writes" target="_blank" rel="noopener noreferrer">&lt;code>full_page_writes&lt;/code>&lt;/a>, which is enabled by default.
With it, when we modify any database page for the first time after a checkpoint, it doesn’t only WAL log the modified row, instead it logs the entire page, 8kB.
In the above query, for every update, we modify 100 randomly selected rows in a table.
Because they are randomly selected from millions of rows, they have a good chance of being on different pages.
Let’s say that with a specific dataset, on average we hit a 10% new page rate – one update has to do 10 full page writes.
That means every single UPDATE we execute generates more than 80kB of WAL.&lt;/p>
&lt;p>This is again an oversimplification, but it is a good enough approximation without going into details too much.&lt;/p>
&lt;p>With 15,000 QPS, that’s around 1.2 GB/s of WAL.
From the table at the beginning, on my test PC AES-128-CTR has around 8 GB/s single core bandwidth.
1.2 GB/s is 15% of that – and that assumes no context switching or other losses, meaning no matter how good the integration into the database is, if we do encryption at this volume while holding a global lock, we have to expect at least 15% performance drop with these numbers.
In practice it’s a bit more than that.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>I hope this explanation was more useful than a single graph showcasing how good our performance with pg_tde is.
The worst-case scenarios I described here are corner cases, most servers don’t generate sustained gigabytes per second of WAL, even in production.&lt;/p>
&lt;p>But I still think this is important to mention, as it is an easily overlooked detail caused by the combination of PostgreSQL’s WAL architecture and the simple way most vendors add encryption to it.&lt;/p>
&lt;p>I also didn’t go into detail on many parts of this post.
If I tried to explain everything in absolute detail, this would be ten times longer, and much harder to follow and understand.
I do plan to touch on some of these subjects in separate blog posts, where we can focus only on those topics in more depth.
If you have questions or suggestions about the topic, don’t hesitate to reach out using our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">community forums&lt;/a>!&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>TDE</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2026/07/pg_tde_superfast_hu_371b9bab6b04c1ea.jpg"/><media:content url="https://percona.community/blog/2026/07/pg_tde_superfast_hu_85df816a2ff41172.jpg" medium="image"/></item><item><title>Perconians at WeAreDevelopers World Congress 2026: Agents Everywhere, Security Wake-Up Calls, and Buzzword Bingo</title><link>https://percona.community/blog/2026/07/17/wearedevelopers-2026/</link><guid>https://percona.community/blog/2026/07/17/wearedevelopers-2026/</guid><pubDate>Fri, 17 Jul 2026 11:00:00 UTC</pubDate><description>On July 9–10, the two of us - Sandra (Engineering, Percona for MongoDB) and Radek (Product, Percona for MongoDB) - packed our backpacks and headed to Berlin for the WeAreDevelopers World Congress Europe 2026 (WAD). The 11th edition of the congress gathered 15,000 developers and 500+ speakers for two intense days, and we came back with full notebooks, fresh ideas, and one very clear message from the industry.</description><content:encoded>&lt;p>On July 9–10, the two of us - &lt;strong>Sandra&lt;/strong> (Engineering, Percona for MongoDB) and &lt;strong>Radek&lt;/strong> (Product, Percona for MongoDB) - packed our backpacks and headed to Berlin for the &lt;a href="https://www.wearedevelopers.com/world-congress" target="_blank" rel="noopener noreferrer">WeAreDevelopers World Congress Europe 2026&lt;/a> (WAD). The 11th edition of the congress gathered &lt;strong>15,000 developers and 500+ speakers&lt;/strong> for two intense days, and we came back with full notebooks, fresh ideas, and one very clear message from the industry.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="768" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2026/07/wearedevelopers_hu_b3b82a20b180331.webp 768w, https://percona.community/blog/2026/07/wearedevelopers_hu_2a92841bb0f59830.webp 480w, https://percona.community/blog/2026/07/wearedevelopers_hu_81a3f16dca96b78.webp 1024w" src="https://percona.community/blog/2026/07/wearedevelopers_hu_81a3f16dca96b78.webp" alt="Sandra and Radek at WeAreDevelopers" loading="lazy" />&lt;figcaption>Sandra and Radek in front of Berlin Messe hosting WeAreDevelopers Europe 2026.&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1024" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2026/07/wearedevelopers-expo_hu_27d49053f91b4919.webp 768w, https://percona.community/blog/2026/07/wearedevelopers-expo_hu_3f0e88b74c969fc5.webp 480w, https://percona.community/blog/2026/07/wearedevelopers-expo_hu_968fc5155dc87ee5.webp 1024w" src="https://percona.community/blog/2026/07/wearedevelopers-expo_hu_968fc5155dc87ee5.webp" alt="WeAreDevelopers World Congress 2026 impressions" loading="lazy" />&lt;figcaption>From the keynote stage to the expo floor at WeAreDevelopers World Congress Europe 2026 in Berlin.&lt;/figcaption>&lt;/figure>
At Percona, we genuinely love getting out of our daily routine to learn what’s new. It is incredibly refreshing to escape the daily grind of sprints and stand-ups just to listen and absorb. Hearing how other teams are tackling scale and complexity reminds us that we are all solving different flavors of the same core problems. These events act as a catalyst for innovation, sparking conversations that inevitably push our own boundaries. Ultimately, taking a couple of days to zoom out and see where the industry is heading helps us build what Sandra calls a &lt;em>mental index&lt;/em> - concepts that might not solve today’s ticket, but will absolutely pay off six months from now.&lt;/p>
&lt;p>While we took notes across a huge variety of topics, one common thread stood out above the rest. This post is our attempt to share that index with you, with links so you can dig deeper into whatever catches your eye.&lt;/p>
&lt;p>Spoiler: if you played buzzword bingo with “agentic AI,” you’d have won in the first hour.&lt;/p>
&lt;h2 id="the-one-big-theme-agentic-ai-surprise">The one big theme: Agentic AI (surprise!)&lt;/h2>
&lt;p>Every conference right now has an AI theme, but WAD went deep: adoption stories, best practices, building and optimizing RAG pipelines, and - importantly - what happens to security when agents write and ship code.&lt;/p>
&lt;p>Our personal top takeaways:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Security matters more than ever in the age of AI.&lt;/strong> More on that below - this one deserves its own section.&lt;/li>
&lt;li>&lt;strong>Agents are only as good as their context.&lt;/strong> Intent, mission, purpose, style, goals, success metrics - the teams winning with AI are the ones writing this down for their agents.&lt;/li>
&lt;li>&lt;strong>Understand &lt;em>why&lt;/em> you do what you do.&lt;/strong> Agents won’t think about that for us. The engineering judgment moves up the stack; it doesn’t disappear.&lt;/li>
&lt;/ul>
&lt;h2 id="the-sdlc-is-dead---the-agentic-assembly-line-keynote">“The SDLC is dead” - the Agentic Assembly Line keynote&lt;/h2>
&lt;p>Thomas Dohmke (CEO of Entire, previously CEO of GitHub) opened with a demo-filled keynote about where developers and agents are headed. A few numbers that made the whole room sit up:&lt;/p>
&lt;ul>
&lt;li>Teams using coding agents ship &lt;strong>5× more code&lt;/strong> (measured by PRs), and PRs are &lt;strong>3× bigger&lt;/strong> than 18 months ago.&lt;/li>
&lt;li>&lt;strong>20%+ of AI-generated changes are accepted without human review.&lt;/strong> (Brave? Terrifying? Discuss.)&lt;/li>
&lt;li>Some products - Codex, notably - are now written &lt;em>only&lt;/em> by AI.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1761" height="833" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/githubstats_hu_46ce79ac46f0514e.webp 768w, https://percona.community/blog/2026/07/githubstats_hu_f707829fd1311a3.webp 480w, https://percona.community/blog/2026/07/githubstats_hu_b124e3d7f8f8f626.webp 1400w" src="https://percona.community/blog/2026/07/githubstats_hu_b124e3d7f8f8f626.webp" alt="GitHub Stats" loading="lazy" />&lt;figcaption>GitHub statistics&lt;/figcaption>&lt;/figure>
&lt;figure>&lt;img width="1717" height="852" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/claudecodestats_hu_ff1d2ea81698b115.webp 768w, https://percona.community/blog/2026/07/claudecodestats_hu_d76f1174e79a4298.webp 480w, https://percona.community/blog/2026/07/claudecodestats_hu_dd050fa5d075aad7.webp 1400w" src="https://percona.community/blog/2026/07/claudecodestats_hu_dd050fa5d075aad7.webp" alt="Claude Code Stats" loading="lazy" />&lt;figcaption>Claude Code statistics&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>His conclusion: &lt;strong>the Software Development Lifecycle as we know it is dead.&lt;/strong> The DevOps loop is evolving into what he called &lt;a href="https://medium.com/@tentenco/what-is-ralph-loop-a-new-era-of-autonomous-coding-96a4bb3e2ac8" target="_blank" rel="noopener noreferrer">“The Ralph Loop”&lt;/a> - a much faster path from code to production, with developers acting as verifiers, because agents still fail often. The winning team, in his view, is the one whose agents understand the company’s mission, values, and purpose.&lt;/p>
&lt;p>One practical problem he highlighted: when you code with agents, context fragments across chats, prompts, sessions, and branches. Tools like &lt;a href="https://entire.io/" target="_blank" rel="noopener noreferrer">entire.io&lt;/a> now store the chat sessions behind each PR right in the GitHub repository - so the &lt;em>intent&lt;/em> behind a change doesn’t evaporate. You may want to check this tool out! Let’s see how the GitHub, we know today, evolves over the next decade.&lt;/p>
&lt;h2 id="retrieval-is-the-weakest-link-in-your-rag">Retrieval is the weakest link in your RAG&lt;/h2>
&lt;p>One of our favorite technical talks, by Tomek Porozynski (deepsense.ai), tackled the “R” in RAG (Retrieval Augmented Generation). General embedding models are trained on public data - they know general language, &lt;strong>not your business&lt;/strong>.&lt;/p>
&lt;p>Basic retrieval falls short: keyword search misses semantic context, and vector search misses exact terms, multi-step logic, document-wide context, and internal jargon. There’s no single fix - you pick the right tool for the job.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="472" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2026/07/rag-fixes_hu_2b647fc831664a15.webp 768w, https://percona.community/blog/2026/07/rag-fixes_hu_3a62890364c63970.webp 480w, https://percona.community/blog/2026/07/rag-fixes_hu_53ecdb67ee3a68e2.webp 1024w" src="https://percona.community/blog/2026/07/rag-fixes_hu_53ecdb67ee3a68e2.webp" alt="How to fix your RAG" loading="lazy" />&lt;figcaption>Matching each retrieval challenge to a potential fix.&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>For domain-specific knowledge, the fix is to fine-tune the embedding model on your own domain, so the vector space itself shifts to reflect your terminology and the real relationships between your terms.&lt;/p>
&lt;p>The mechanics are surprisingly approachable: reshape the vector space through relative distances (pull matching pairs closer, push mismatched pairs apart), using either &lt;strong>triplet loss&lt;/strong> or &lt;strong>MultipleNegativesRankingLoss&lt;/strong> - and with the &lt;a href="https://sbert.net/" target="_blank" rel="noopener noreferrer">Sentence Transformers&lt;/a> toolkit, the latter is literally one import away.&lt;/p>
&lt;p>If you want to try it yourself, the speaker shared &lt;a href="https://github.com/ontaptom/workshops/tree/main/notebooks" target="_blank" rel="noopener noreferrer">hands-on Colab notebooks&lt;/a>.&lt;/p>
&lt;h2 id="the-security-wake-up-call-surviving-the-vulnpocalypse">The security wake-up call: surviving the “Vulnpocalypse”&lt;/h2>
&lt;p>Adrian Mouat (Chainguard) delivered the talk that stuck with us the most. Advanced AI models can now autonomously discover and weaponize zero-day vulnerabilities at machine speed - effectively &lt;strong>erasing the traditional patch window&lt;/strong>. Especially with the rise of &lt;a href="https://www.anthropic.com/claude/mythos" target="_blank" rel="noopener noreferrer">Anthrophic Mythos&lt;/a> model, this might lead to Vulnpocalypse!&lt;/p>
&lt;p>This is very serious for open source: attackers can point LLMs at public codebases, while underfunded maintainers face an overwhelming volume of newly discovered bugs. As people who live and breathe open source databases, this hits close to home.&lt;/p>
&lt;p>The defenses he proposed:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Fight AI with AI&lt;/strong> - proactively scan your own infrastructure and find vulnerabilities before attackers do.&lt;/li>
&lt;li>&lt;strong>Minimize your attack surface&lt;/strong> - fewer dependencies, and consider AI-written snippets over pulling in vulnerable third-party libraries.&lt;/li>
&lt;li>&lt;strong>Strict hygiene&lt;/strong> - immediate patching and eliminating long-lived access tokens are non-negotiable.&lt;/li>
&lt;li>&lt;strong>Industry coalitions&lt;/strong> - rapid-response groups like &lt;a href="https://www.chainguard.dev/athena" target="_blank" rel="noopener noreferrer">Athena&lt;/a> share mitigations at machine speed, while “Akrites” safely funnels fixes back into upstream open source projects.&lt;/li>
&lt;/ul>
&lt;p>At Percona, we’re already evaluating joining these coalitions - stay tuned!&lt;/p>
&lt;p>Related: Isha Salania (Microsoft) showed how &lt;strong>confidential computing&lt;/strong> extends encryption to data &lt;em>in use&lt;/em> - your prompts, retrieved chunks, and keys living in encrypted memory. For anyone building sovereign RAG systems on top of databases, this end-to-end view of data protection is worth understanding - and it’s going to raise expectations for queryable encryption across the whole database ecosystem.&lt;/p>
&lt;h2 id="mcp-doesnt-suck---your-agent-does">“MCP doesn’t suck - your agent does”&lt;/h2>
&lt;p>Best talk title of the conference, courtesy of Jan Curn (Apify). The problem: most agents load &lt;em>all&lt;/em> available tool schemas into the context window upfront, causing context rot, slow performance, and rapidly burning tokens. Their answer is &lt;strong>mcpc&lt;/strong> - a lightweight CLI that enables &lt;em>progressive tool discovery&lt;/em>: the agent fetches only the tool schemas it needs, on demand, and chains workflows through native code execution. Add OAuth 2.1 and sandboxed proxy connections, and you get a much saner MCP setup.&lt;/p>
&lt;h2 id="more-gems-worth-your-time">More gems worth your time&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>From SDLC to ADLC.&lt;/strong> Marcin Wawryszczuk (Andersen) argued that AI speeds up &lt;em>coding&lt;/em> but not the &lt;em>release cycle&lt;/em> - the industry needs an Agentic Delivery Lifecycle where agents help with requirements, architecture, docs, and pipelines, while engineers keep authority over architecture, governance, and validation.&lt;/li>
&lt;li>&lt;strong>Don’t lock in your AI tooling too early.&lt;/strong> Angie Jones shared how Block bought access to many tools and let engineers run with them - what works for a web developer doesn’t work for a mobile or JVM developer, and that diversity of feedback is gold. Standardize when you see workflows succeeding repeatedly, not because a vendor made a good pitch.&lt;/li>
&lt;li>&lt;strong>LLMs in the wild.&lt;/strong> GetYourGuide’s data scientist Giampaolo Casolla and MLOps engineer Steven Mi walked through keeping an AI-driven recommendation system alive in production. Real numbers, real trade-offs.&lt;/li>
&lt;li>&lt;strong>Platform-as-a-Product.&lt;/strong> Dominik Schmidle (Giant Swarm) on why internal platforms fail: happy users won’t save your platform if the C-level sees it as pure cost. Know your user &lt;em>and&lt;/em> your decision-maker - and do internal marketing.&lt;/li>
&lt;li>&lt;strong>Werner Vogels (CTO, Amazon) fireside chat.&lt;/strong> Invisible work is important and worth sharing. Stay curious, never stop learning - and he recommended the book &lt;a href="https://www.amazon.de/Ask-Your-Developer-Software-Developers/dp/0063018292" target="_blank" rel="noopener noreferrer">&lt;em>Ask Your Developer&lt;/em>&lt;/a> by Jeff Lawson.&lt;/li>
&lt;/ul>
&lt;h2 id="the-expo-floor">The expo floor&lt;/h2>
&lt;p>Between the talks, we’ve also hung out at the Percona booth - yes, we’ve been there the entire two days and chatting with 100+ visitors about what we love the most - databases!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="768" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2026/07/perconabooth_hu_e42b5bac816ad028.webp 768w, https://percona.community/blog/2026/07/perconabooth_hu_48ed4a688fe2f0c7.webp 480w, https://percona.community/blog/2026/07/perconabooth_hu_24cce432e2e6704d.webp 1024w" src="https://percona.community/blog/2026/07/perconabooth_hu_24cce432e2e6704d.webp" alt="Percona booth" loading="lazy" />&lt;figcaption>Percona Booth at WeAreDevelopers 2026&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>We’ve also visited our neighbours at the expo hall and had great conversations with them, too - but there was one that stood out:&lt;/p>
&lt;p>&lt;a href="https://www.qodo.ai/" target="_blank" rel="noopener noreferrer">&lt;strong>Qodo&lt;/strong>&lt;/a> (formerly CodiumAI): AI code review that indexes your entire repository, so reviews understand the architectural “why” behind a change - and it’s &lt;a href="https://github.com/marketplace/qodo-merge-pro-for-open-source" target="_blank" rel="noopener noreferrer">free for open source projects&lt;/a>. We’re excited to try it on our own projects.&lt;/p>
&lt;h2 id="parting-words">Parting words&lt;/h2>
&lt;p>Two quotes from the WeAreDevelopers founders stayed with us. From CPO Thomas Pamminger:&lt;/p>
&lt;blockquote>
&lt;p>AI didn’t make me worse at my job - it made it easier to be worse without noticing. Charles Eames, asked what he’d delegate, said: &lt;strong>never the understanding.&lt;/strong>&lt;/p>&lt;/blockquote>
&lt;p>And from CEO Sead Ahmetović:&lt;/p>
&lt;blockquote>
&lt;p>Someone, somewhere, will depend on what you ship next. That’s not a burden - that’s the whole point, because your work matters. Let’s do it well.&lt;/p>&lt;/blockquote>
&lt;p>That’s a pretty good summary of why we go to these events: to keep understanding, not just shipping.&lt;/p>
&lt;p>If any of the topics above sparked something - RAG fine-tuning, supply chain security, vector search in databases - chat with us on the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a> or just drop a comment here. We’d love to hear what &lt;em>you&lt;/em> took away from WAD if you were there.&lt;/p>
&lt;p>See you at the next event!&lt;/p></content:encoded><author>Radoslaw Szulgo</author><author>Sandra Romanchenko</author><category>Events</category><category>Community</category><category>Open Source</category><category>AI</category><category>Community Ascent</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2026/07/wearedevelopers_hu_2764d0ae6abb5c88.jpeg"/><media:content url="https://percona.community/blog/2026/07/wearedevelopers_hu_9e719d1afbc3ad3b.jpeg" medium="image"/></item><item><title>Introducing Mountaineers: A Way to Say Thank You</title><link>https://percona.community/blog/2026/07/16/introducing-mountaineers/</link><guid>https://percona.community/blog/2026/07/16/introducing-mountaineers/</guid><pubDate>Thu, 16 Jul 2026 11:00:00 UTC</pubDate><description>You filed a bug report at 11pm because you’d already done the work to isolate it. You answered a forum question that had been sitting unanswered for three days. You wrote a PR. You spent an hour on a call telling us what’s broken about a tool you use every day. None of that is small, and none of it should go unnoticed.</description><content:encoded>&lt;p>You filed a bug report at 11pm because you’d already done the work to isolate it. You answered a forum question that had been sitting unanswered for three days. You wrote a PR. You spent an hour on a call telling us what’s broken about a tool you use every day. None of that is small, and none of it should go unnoticed.&lt;/p>
&lt;p>That’s what Mountaineers are. It’s how we recognize the time and energy you put into this community — and reward it.&lt;/p>
&lt;h2 id="why-we-built-this">Why we built this&lt;/h2>
&lt;p>Every contribution to this community costs you something: time, expertise, patience. Writing reproduction steps for a bug isn’t free. Neither is answering the same kind of question for the fifth newcomer this month, or sitting down with our engineering team to walk through how you actually use Percona Operators in production.&lt;/p>
&lt;p>We see that. Mountaineers are our way of tracking it properly and giving something back — recognition, access, and yes, swag.&lt;/p>
&lt;h2 id="what-counts">What counts&lt;/h2>
&lt;p>This isn’t just about code. If you’ve assumed that contributing means opening a pull request or nothing, that’s not how this works. Points come from:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GitHub&lt;/strong> — issues, PRs, and merged contributions&lt;/li>
&lt;li>&lt;strong>Forum&lt;/strong> — starting discussions, replying, and accepted solutions&lt;/li>
&lt;li>&lt;strong>Content&lt;/strong> — blog posts, tutorials, and video appearances, including through our &lt;a href="https://percona.community/blog/2026/05/22/write-for-percona-community/" target="_blank" rel="noopener noreferrer">Community Writers Program&lt;/a>, where you also get paid for published posts&lt;/li>
&lt;li>&lt;strong>Direct feedback&lt;/strong> — 1:1 sessions with our engineering team and survey responses&lt;/li>
&lt;/ul>
&lt;p>That last one matters more than people think. If you want to tell us what works, what doesn’t, and how you’re actually using our tools day to day, we want that conversation. Talk to engineering directly, or write it up for the blog. Either way, it counts.&lt;/p>
&lt;h2 id="how-the-climb-works">How the climb works&lt;/h2>
&lt;p>Everyone starts at Basecamp. From there, the more you contribute — and the more places you contribute — the higher you climb. Show up across GitHub, the forum, and content in the same month, and your points multiply. We’re not trying to make this complicated: more engagement, more recognition, faster.&lt;/p>
&lt;p>Points convert into real rewards. Stickers and digital badges at the entry tier. T-shirts and water bottles as you climb. Hoodies and tech accessories further up. All those who begin the climb will receive a serialized Challenge Coin — the kind of thing you can’t buy, only earn.&lt;/p>
&lt;p>The people who consistently show up across the board get invited to take part in Percona Live: roadmap sessions, early access, time with the people building the tools you use.&lt;/p>
&lt;h2 id="you-dont-need-a-long-resume-to-start">You don’t need a long resume to start&lt;/h2>
&lt;p>If you’ve filed one bug report with clear reproduction steps, answered one forum question, or have an opinion about a tool you use that you’ve never told us — you already have something to bring. We built Mountaineers to recognize the full range of ways people show up, not just the most visible ones.&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSeH_ube0G4TLMP_2Kh5oa1OSSNEaUExiIp1E6qzfUzg9dDD9Q/viewform" target="_blank" rel="noopener noreferrer">Join Mountaineers&lt;/a>&lt;/strong> - a two-minute form. Give us your name, email, role, database of choice, forum username, and where you’re based, and your existing GitHub and forum activity starts counting from that point on.&lt;/p>
&lt;p>Your forum account is still where you post and answer questions — this form is what gets you on the leaderboard and matches that activity to you. Don’t have a forum account yet? &lt;a href="https://forums.percona.com/signup" target="_blank" rel="noopener noreferrer">Sign up here first&lt;/a>, then come back and add your username.&lt;/p>
&lt;p>Want the full detail on points, rungs, and rewards? Read the &lt;a href="https://percona.community/ascent/mountaineers/" target="_blank" rel="noopener noreferrer">Mountaineers program page&lt;/a>.&lt;/p></content:encoded><author>Laura Czajkowski</author><category>Community</category><category>Percona</category><category>Open Source</category><category>Mountaineers</category><category>Community Ascent</category><media:thumbnail url="https://percona.community/blog/2026/07/mountaineers-intro_hu_bb481251c2206589.jpg"/><media:content url="https://percona.community/blog/2026/07/mountaineers-intro_hu_ec54f82873f1d403.jpg" medium="image"/></item><item><title>pg_tde: our fork is temporary, our commitment to open TDE is not</title><link>https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/</link><guid>https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/</guid><pubDate>Wed, 08 Jul 2026 00:00:00 UTC</pubDate><description>Recently we noticed a LinkedIn post promoting open_pg_tde, a fork of our pg_tde, claiming to be more open. I looked at the repository, and have to disagree with their claim. In this blog post, I’ll explain why.</description><content:encoded>&lt;p>Recently we noticed a LinkedIn post promoting &lt;a href="https://github.com/commandprompt/open_pg_tde" target="_blank" rel="noopener noreferrer">open_pg_tde&lt;/a>, a fork of our &lt;a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">pg_tde&lt;/a>, claiming to be more open.
I looked at the repository, and have to disagree with their claim.
In this blog post, I’ll explain why.&lt;/p>
&lt;p>The short version: open_pg_tde needs the exact same modified PostgreSQL that pg_tde does – TDE isn’t possible without those upstream changes.
The only difference is delivery: they ship those changes as a patch file users have to apply by hand, while we provide a ready-made branch.&lt;/p>
&lt;h2 id="upstreaming-tde">Upstreaming TDE!&lt;/h2>
&lt;p>The main point of open_pg_tde is that it is more open, as it is not gated behind a vendor fork.
A bit later I’ll go into details why I think that’s incorrect from a technical point of view, but before that, I want to make something clear:
we do not want to keep TDE in our fork, we are actively working on upstreaming it!&lt;/p>
&lt;p>The LinkedIn post also linked to a pgedge blog post written at the end of May, &lt;a href="https://www.pgedge.com/blog/why-postgres-lacks-transparent-data-encryption" target="_blank" rel="noopener noreferrer">Why Postgres Lacks Transparent Data Encryption&lt;/a>.&lt;/p>
&lt;p>While it seems to be optimistic about the future of encryption in the community version, it also missed that we had not &lt;a href="https://2026.pgconf.dev/session/559" target="_blank" rel="noopener noreferrer">one&lt;/a>, but &lt;a href="https://2026.pgconf.dev/session/738" target="_blank" rel="noopener noreferrer">two&lt;/a> sessions about it at pgconf.dev, the first one organized by Kai Wagner from Percona, and the second one by Ants Aasma from Cybertec.&lt;/p>
&lt;p>We had some really good discussions during those sessions, and ended up with lots of “homework”:
things we wanted to explore and benchmark before continuing the discussion on pgsql-hackers, where the discussion will soon continue.&lt;/p>
&lt;p>With this I want to emphasize that Percona is 100% behind making PostgreSQL transparent data encryption open, as part of the community.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1448" height="1086" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_a46ef88fe5213b3c.webp 768w, https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_c445c3b31d5cdf9b.webp 480w, https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_c6c636d3ee557f1c.webp 1400w" src="https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_c6c636d3ee557f1c.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our fork was born out of necessity, not because we wanted to have our own version.
In fact, if you look back into the &lt;a href="https://github.com/percona/pg_tde/commits/main/" target="_blank" rel="noopener noreferrer">git history of pg_tde&lt;/a>, or at our &lt;a href="https://www.percona.com/blog/protect-your-postgresql-database-with-pg_tde-safe-and-secure/" target="_blank" rel="noopener noreferrer">earlier blog posts&lt;/a>, we first tried to make it work without any upstream patch. Unfortunately, that didn’t work out.&lt;/p>
&lt;h2 id="why-do-we-have-our-fork">Why do we have our fork?&lt;/h2>
&lt;p>The open_pg_tde documentation publishes the following comparison table:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2090" height="789" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/open_pg_tde_comparison_hu_f9047f222a151c45.webp 768w, https://percona.community/blog/2026/07/open_pg_tde_comparison_hu_aafc873c9fb74c1e.webp 480w, https://percona.community/blog/2026/07/open_pg_tde_comparison_hu_c33bcf3f5670f308.webp 1400w" src="https://percona.community/blog/2026/07/open_pg_tde_comparison_hu_c33bcf3f5670f308.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The table is clearly AI generated, and has some misleading and/or inconsistent elements.
Here is what we think a more honest comparison looks like:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Aspect&lt;/th>
&lt;th>pg_tde&lt;/th>
&lt;th>open_pg_tde&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Requires a patched PostgreSQL&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>How the patches are delivered&lt;/td>
&lt;td>Ready-made fork/branch&lt;/td>
&lt;td>Patch file, applied by hand&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Open source patches, cherry-pickable&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Actually vendor locked&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>API-version safety check&lt;/td>
&lt;td>Yes (&lt;code>PERCONA_API_VERSION&lt;/code>)&lt;/td>
&lt;td>No, it was removed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Guards against mismatched-package corruption&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Supports PostgreSQL 16, 17, 18&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encrypts temporary files at rest&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Supports all KMIP- and Vault-compatible key providers&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I’ll leave out the discussion about temporary files in this blog post.
That’s a complex topic by itself, it raises many questions, and we have good reasons why we are still thinking about it instead of shipping a quick version prototyped with Claude.
It is something I plan to talk about later, on its own.&lt;/p>
&lt;p>Back to the comparison: it claims that pg_tde is vendor locked because it only works with our fork.
I don’t think that’s the case:
Our fork is completely open source, anybody can go to our &lt;a href="https://github.com/percona/postgres/" target="_blank" rel="noopener noreferrer">GitHub&lt;/a> and cherry-pick our patches manually.&lt;/p>
&lt;p>open_pg_tde doesn’t remove the need for these patches: it can’t, TDE requires them.
It just ships them as a patch file users apply manually, rather than as a branch.
Both approaches modify PostgreSQL identically.&lt;/p>
&lt;p>The fork exists for one sole reason:
convenience, we want to make the life of our users easier.
Checking out a fork is easier than manually applying patch files.&lt;/p>
&lt;p>So the difference between pg_tde and open_pg_tde is not &lt;em>whether&lt;/em> you patch PostgreSQL, you do, either way.
It’s &lt;em>how&lt;/em> those patches reach you.&lt;/p>
&lt;h2 id="percona_api_version">PERCONA_API_VERSION&lt;/h2>
&lt;p>The open_pg_tde commit that &lt;a href="https://github.com/commandprompt/open_pg_tde/commit/55ed7f1c4ad5bb31ab085e378699377c04158d09#diff-2b9df17a765260aa1b6bc32fedb30dee1d5f80f975252c2bf6308e5097591aac" target="_blank" rel="noopener noreferrer">removes “vendor lock-in”&lt;/a> is mainly a documentation change:
it adds the patch file, and asks users to apply it manually.&lt;/p>
&lt;p>It has one single code change other than that:
in our upstream fork we define &lt;code>PERCONA_API_VERSION&lt;/code> and check against it, and open_pg_tde removes both.&lt;/p>
&lt;p>While it has PERCONA in its name, and because of that it has been misinterpreted as vendor lock-in, that isn’t why we added it:
it’s to prevent accidents.&lt;/p>
&lt;p>And with encryption, which modifies the storage of data, &lt;strong>accidents might mean data corruption&lt;/strong>.&lt;/p>
&lt;p>PostgreSQL normally is very stable:
minor versions contain only bugfixes, the API/ABI is very stable, a minor upgrade is considered easy and safe.&lt;/p>
&lt;p>However, when you start applying patches manually, you break this promise:
is your patch as stable as PostgreSQL itself?&lt;/p>
&lt;p>The reality is that it’s not.
pg_tde is backported to earlier major versions. Both pg_tde and open_pg_tde support PostgreSQL 16, and we could backport it to even earlier versions.&lt;/p>
&lt;p>This means that the patch has to apply to multiple major versions, and if we have to make a breaking change in it?
Then we have to make that change in all major versions!&lt;/p>
&lt;p>Our API version isn’t about locking users to our fork, it’s a safety net.
It is there to make sure that upgrades happen correctly, and our users don’t accidentally mix pg_tde and PostgreSQL packages that aren’t 100% compatible, but seem to work.&lt;/p>
&lt;p>Imagine this:&lt;/p>
&lt;ol>
&lt;li>You have a working PostgreSQL + pg_tde installation&lt;/li>
&lt;li>There’s an update, you upgrade PostgreSQL&lt;/li>
&lt;li>What you didn’t notice is that we also updated pg_tde.
You forgot to update that package, or for some reason intentionally didn’t update it yet, and because we didn’t break the API boundary, everything seems to work.
Except, we did change some internal details of how our patch works.
It doesn’t cause any visible issues at first, but slowly some pages of your database are becoming unreadable, or you start getting segmentation faults when accessing a table…&lt;/li>
&lt;/ol>
&lt;p>The above of course is a hypothetical scenario, we didn’t release any dangerous update like that.&lt;/p>
&lt;p>But the point is, even if we have to, in pg_tde, we have safeguards.
In our solution the API version check catches this: at the 3rd step, instead of a slow data corruption, we present you with an early error stating that you should fix your system.&lt;/p>
&lt;p>In the above scenario, let’s say that in the first step you have a working installation where both the server and pg_tde have &lt;code>PERCONA_API_VERSION=1&lt;/code>.
After that, you only update the server, and the new version now has &lt;code>PERCONA_API_VERSION=2&lt;/code>.
Since the extension is still at the previous version 1, the server will fail immediately at startup, reminding you that these two packages are not compatible.&lt;/p>
&lt;p>Maybe we could have called it something different, &lt;code>TDE_PATCH_VERSION&lt;/code>, or something like that.
But the goal is still the same, it is an important safeguard, please do not remove it!
Currently open_pg_tde doesn’t protect against this type of mismatch.&lt;/p>
&lt;p>In fact, in our latest release, not yet included in open_pg_tde, we did increment PERCONA_API_VERSION because of a slightly incompatible change.
We don’t expect any data corruption possibilities from it, it was a very minor API change, but we want to play things safe, as keeping the data of our users secure is our first priority.&lt;/p>
&lt;h2 id="patches-welcome">Patches welcome!&lt;/h2>
&lt;p>If we look into the git history of open_pg_tde, it’s mostly (AI written) documentation changes.
Other than the removal of the API check I explained above, there are two actual code changes:&lt;/p>
&lt;ul>
&lt;li>One is the addition of the AES-XTS algorithm for encrypting relation pages&lt;/li>
&lt;li>Another is the support for temporary file encryption&lt;/li>
&lt;/ul>
&lt;p>We would be happy to start a discussion of any of these features, or even others.
If you want to contribute, please do not hesitate, and contact us.&lt;/p>
&lt;p>This is the strength of open source: it allows contributions and open discussions.
While the team behind open_pg_tde, or anybody else, can of course fork pg_tde this way (because we are open and do not gatekeep features), the energy spent on that could be used to make pg_tde better in a shared community effort.&lt;/p>
&lt;p>You can open a &lt;a href="https://github.com/percona/pg_tde/pulls" target="_blank" rel="noopener noreferrer">Pull Request&lt;/a>, or a &lt;a href="https://github.com/percona/pg_tde/issues" target="_blank" rel="noopener noreferrer">GitHub Issue&lt;/a>, or even use our &lt;a href="https://perconadev.atlassian.net/projects/PG/issues/" target="_blank" rel="noopener noreferrer">Jira&lt;/a>, all options are equally good!&lt;/p>
&lt;p>Even an issue/PR stating “please call PERCONA_API_VERSION differently”, we are not against changing that if the consensus is that it should be something different.&lt;/p>
&lt;p>But please don’t create forks without even reaching out to your “upstream”.
That only increases fragmentation, and makes the life of our common audience harder.&lt;/p>
&lt;p>What we ask for is simple: let’s make PostgreSQL better with our work, not harder to use.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/07/open_pg_tde_do_not_fork_hu_4d2f4f9d5d23a4ff.webp 768w, https://percona.community/blog/2026/07/open_pg_tde_do_not_fork_hu_9e95049a90d0507c.webp 480w, https://percona.community/blog/2026/07/open_pg_tde_do_not_fork_hu_ab34eca0737cf93b.webp 1400w" src="https://percona.community/blog/2026/07/open_pg_tde_do_not_fork_hu_ab34eca0737cf93b.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>TDE</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_ea72a516d04e9d7a.jpg"/><media:content url="https://percona.community/blog/2026/07/pg_tde_upstreaming_hu_d209075dd80b545d.jpg" medium="image"/></item><item><title>Cross-site Disaster Recovery with Percona Operator for MySQL</title><link>https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/</link><guid>https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/</guid><pubDate>Mon, 06 Jul 2026 10:00:00 UTC</pubDate><description>A MySQL InnoDB Cluster provides high availability for a single database cluster using Group Replication. This works well for node failures inside the cluster, but disaster recovery usually requires another cluster in a separate location: another Kubernetes cluster, region, data center, or cloud.</description><content:encoded>&lt;p>A MySQL InnoDB Cluster provides high availability for a single database cluster using Group Replication. This works well for node failures inside the cluster, but disaster recovery usually requires another cluster in a separate location: another Kubernetes cluster, region, data center, or cloud.&lt;/p>
&lt;p>This replica cluster needs to stay in sync with the primary, remain protected from accidental writes, and be ready to take over when you need to move traffic, either as a planned operation or during an outage.&lt;/p>
&lt;p>&lt;a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset.html" target="_blank" rel="noopener noreferrer">InnoDB ClusterSet&lt;/a> addresses this by linking multiple MySQL clusters into a single disaster-recovery topology. One cluster handles writes, while the others stay synchronized as read-only replicas.&lt;/p>
&lt;p>Starting from v1.2.0, the Percona Operator for MySQL adds a new custom resource, &lt;code>PerconaServerMySQLClusterSet&lt;/code>, which allows managing InnoDB ClusterSets. Creating the ClusterSet, adding replicas, switching the primary, and performing a forced failover are all handled declaratively by updating the Kubernetes spec and letting the operator reconcile the desired state.&lt;/p>
&lt;p>This post explains how ClusterSet works, how to set it up with the Percona Operator, and how planned switchovers and emergency failovers work in practice.&lt;/p>
&lt;h2 id="understanding-innodb-clusterset">Understanding InnoDB ClusterSet&lt;/h2>
&lt;p>Any disaster recovery design usually comes down to two important numbers:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Recovery Point Objective&lt;/strong>, or RPO, is how much data you can afford to lose. For example, an RPO of five seconds means the business can tolerate losing up to five seconds of writes.&lt;/li>
&lt;li>&lt;strong>Recovery Time Objective&lt;/strong>, or RTO, is how long the system can be unavailable before service must be restored.&lt;/li>
&lt;/ul>
&lt;p>The way you design and operate a ClusterSet directly affects both. To understand why, it helps to first look at the architecture.&lt;/p>
&lt;p>An InnoDB ClusterSet is built from two or more InnoDB Clusters. Each InnoDB Cluster is a Group Replication group. In other words, it is the same kind of highly available MySQL cluster that the &lt;a href="https://docs.percona.com/percona-operator-for-mysql/latest/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL&lt;/a> can already deploy and manage.&lt;/p>
&lt;p>A ClusterSet adds another layer on top of those clusters. One cluster is the primary cluster and accepts writes, while the others are replica clusters and remain read-only. The primary sends its changes to each replica using asynchronous replication over a dedicated replication channel.&lt;/p>
&lt;p>This gives us two layers of replication, each solving a different problem.&lt;/p>
&lt;p>Inside each cluster, Group Replication protects against the loss of individual MySQL nodes. Members are expected to be closer together, usually within the same region or availability zone group. Writes are coordinated by the group, which helps keep the local cluster consistent and highly available.&lt;/p>
&lt;p>Between clusters, asynchronous replication protects against the loss of an entire site. Replica clusters can be located in another region, another Kubernetes cluster, or another cloud provider. Because this replication is asynchronous, long-distance network latency does not slow down writes on the primary cluster.&lt;/p>
&lt;p>But the tradeoff here is that a replica cluster may be slightly behind the primary. The amount of lag depends on write volume, network latency, and the health of the replication channel. If the primary site is lost, any writes that had not yet reached the replica are lost. That lag is the practical data-loss window during an emergency failover. Any transactions that had not replicated before failover could be lost.&lt;/p>
&lt;p>Before building a ClusterSet with the operator, there are a few important requirements to keep in mind:&lt;/p>
&lt;ul>
&lt;li>Every cluster in the ClusterSet must use the Group Replication topology. The operator also supports asynchronous replication with Orchestrator for standalone clusters, but that topology cannot be part of an InnoDB ClusterSet.&lt;/li>
&lt;li>You need MySQL 8.0.27 or later&lt;/li>
&lt;li>Clusters are linked by network address, not by Kubernetes references. A replica cluster only needs to be reachable and managed by an operator. It does not need to live in the same Kubernetes cluster as the primary.&lt;/li>
&lt;/ul>
&lt;p>With the model in place, let’s build a simple cross-site disaster recovery setup.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2026/06/ps-clusterset-architecture.svg" alt="Figure 1. InnoDB ClusterSet overview" />&lt;/figure>&lt;/p>
&lt;h2 id="setting-up-clusterset">Setting up ClusterSet&lt;/h2>
&lt;p>We’ll create the simplest useful ClusterSet: two Group Replication clusters named &lt;code>dc1&lt;/code> and &lt;code>dc2&lt;/code>.&lt;/p>
&lt;p>In this example:
&lt;code>dc1&lt;/code> is the primary cluster.
&lt;code>dc2&lt;/code> is the read-only replica cluster.&lt;/p>
&lt;p>In a real deployment, these would usually run in separate Kubernetes clusters, regions, or cloud environments. The steps are mostly the same. The main requirement is that the endpoints listed in the ClusterSet spec must be routable between sites.&lt;/p>
&lt;h3 id="creating-a-primary-cluster">Creating a primary cluster&lt;/h3>
&lt;p>The primary cluster &lt;code>dc1&lt;/code> is a regular Group Replication cluster. There is nothing ClusterSet-specific about it at this stage.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mysql&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">group-replication&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ... the rest of a normal cluster spec&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can find a complete YAML &lt;a href="https://github.com/percona/percona-server-mysql-operator/blob/main/deploy/cr.yaml" target="_blank" rel="noopener noreferrer">here&lt;/a>. Apply it and wait for it to come up the way you normally would, just as you would for any normal Percona Operator-managed MySQL cluster.&lt;/p>
&lt;h3 id="creating-the-replica-cluster">Creating the replica cluster&lt;/h3>
&lt;p>The replica cluster &lt;code>dc2&lt;/code> is also a Group Replication cluster, but with one important difference:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mysql&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">group-replication&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bootstrap&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">manual &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># &lt;- set this!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Normally, when the operator creates a Group Replication cluster, the first MySQL pod bootstraps the group as soon as it starts. Subsequent pods then join that group.&lt;/p>
&lt;p>For a ClusterSet replica, that is not what we want. We do not want &lt;code>dc2&lt;/code> to form an independent empty cluster. Instead, we want it to receive data from the primary cluster and then join the ClusterSet as a replica.&lt;/p>
&lt;p>With &lt;code>bootstrap.mode: manual&lt;/code>, the first pod starts but does not bootstrap its own Group Replication group. It waits until the ClusterSet process adopts it, clones data from the primary, and then forms the replica cluster. During this stage, the first &lt;code>dc2&lt;/code> pod may remain in a &lt;code>NotReady&lt;/code> state until it is a part of the ClusterSet.&lt;/p>
&lt;h3 id="sharing-cluster-credentials">Sharing cluster credentials&lt;/h3>
&lt;p>The operator automatically creates a &lt;code>clusterset&lt;/code> MySQL user in every cluster and stores its password in the cluster secret.&lt;/p>
&lt;p>The operator uses this user to orchestrate ClusterSet operations, so the password must be the same across all clusters in the ClusterSet. When your clusters are deployed separately, copy the &lt;code>clusterset&lt;/code> value from the primary cluster secret into the replica cluster secret before linking them.&lt;/p>
&lt;p>For example, if &lt;code>dc1&lt;/code> is the primary, copy the &lt;code>clusterset&lt;/code> password from the &lt;code>dc1&lt;/code> secret into the corresponding secret for &lt;code>dc2&lt;/code>.&lt;/p>
&lt;h3 id="linking-the-clusters">Linking the clusters&lt;/h3>
&lt;p>Once both clusters are applied, create a &lt;code>PerconaServerMySQLClusterSet&lt;/code> custom resource.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQLClusterSet&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my-cluster-set&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">finalizers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">percona.com/clusterset-dissolve&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">primaryCluster&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">credentialsSecret&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc1-secrets&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clusterset&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">sslMode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AUTO&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">createReplicaClusterOptions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">recoveryMethod&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clone&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">innodbClusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc1-mysql-primary.default.svc.cluster.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">innodbClusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc2-mysql-0.dc2-mysql.default.svc.cluster.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mysqlshellRunner&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perconalab/percona-server-mysql-operator:main-psmysql8.4&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The most important fields are:&lt;/p>
&lt;ul>
&lt;li>&lt;code>primaryCluster&lt;/code> defines which cluster currently accepts writes. The value must match one of the entries under clusters.&lt;/li>
&lt;li>&lt;code>clusters&lt;/code> lists every member of the ClusterSet and the endpoint the operator should use to reach it. These endpoints are plain network addresses, which is what allows members to run in different Kubernetes clusters or regions.&lt;/li>
&lt;li>&lt;code>credentialsSecret&lt;/code> points to the secret that contains the clusterset user password.&lt;/li>
&lt;li>&lt;code>recoveryMethod: clone&lt;/code> tells the replica cluster to take a full copy of the primary data when it joins the ClusterSet. The alternative is an incremental recovery method, which uses existing binary logs instead of cloning the full dataset.&lt;/li>
&lt;li>&lt;code>mysqlshellRunner&lt;/code> defines the helper pod image used by the operator to run MySQL Shell operations.&lt;/li>
&lt;/ul>
&lt;p>After you apply this resource, the operator starts a MySQL Shell runner pod and creates the ClusterSet on &lt;code>dc1&lt;/code>. It then joins &lt;code>dc2&lt;/code>, which clones the data, starts replication, and brings up the remaining pods in the replica cluster.&lt;/p>
&lt;p>At this point, &lt;code>dc1&lt;/code> serves reads and writes, while &lt;code>dc2&lt;/code> acts as a live read-only copy.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Seeding large replica clusters&lt;/strong>&lt;/p>
&lt;p>In this example, the replica cluster is created with &lt;code>recoveryMethod: clone&lt;/code>, so MySQL Shell provisions the first replica member by copying a physical snapshot from an existing ClusterSet member. That is convenient for medium/small datasets, but it can be fragile across WAN links or very large databases.&lt;/p>
&lt;p>A full clone can take hours, consume significant bandwidth, add load to the donor, run into network interruptions, and become expensive to retry if the operation fails partway through. It can also not be the best fit when the primary is busy or when cross-region egress cost is a concern.&lt;/p>
&lt;p>The operator makes it possible to seed the replica cluster from an existing backup of the primary cluster instead. Create a &lt;code>PerconaServerMySQLBackup&lt;/code> on the primary, restore that backup into the replica cluster with &lt;code>PerconaServerMySQLRestore&lt;/code>, and then add the replica to the ClusterSet using &lt;code>recoveryMethod: incremental&lt;/code>. You can find the exact restore procedure in the &lt;a href="https://docs.percona.com/percona-operator-for-mysql/latest/backups-restore-to-new-cluster.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;p>At that point, the replica already has the primary’s data and GTID history, so ClusterSet only needs to catch it up from the primary’s binary logs instead of transferring the full dataset again.&lt;/p>&lt;/blockquote>
&lt;h3 id="verifying-it-worked">Verifying it worked&lt;/h3>
&lt;p>The simplest way to confirm that the ClusterSet is working is to write data to the primary cluster and read it from the replica.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;ul>
&lt;li>Connect to &lt;code>dc1&lt;/code>.&lt;/li>
&lt;li>Create a test table or insert a row.&lt;/li>
&lt;li>Connect to &lt;code>dc2&lt;/code>.&lt;/li>
&lt;li>Confirm that the same data appears there.&lt;/li>
&lt;/ul>
&lt;p>If the row appears on &lt;code>dc2&lt;/code>, the asynchronous replication channel is running and the replica cluster is receiving changes from the primary.&lt;/p>
&lt;h3 id="planned-switchover">Planned Switchover&lt;/h3>
&lt;p>A planned switchover is used when both clusters are healthy and you intentionally want to move writes from one site to another. This is useful for regional maintenance, Kubernetes cluster upgrades, cloud migrations, or controlled DR testing.&lt;/p>
&lt;p>To move the primary role from &lt;code>dc1&lt;/code> to &lt;code>dc2&lt;/code>, update the primaryCluster field:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl patch ps-clusterset my-cluster-set --type&lt;span class="o">=&lt;/span>merge &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p &lt;span class="s1">'{"spec":{"primaryCluster":"dc2"}}'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The operator notices that the desired primary cluster no longer matches the current primary. It then uses MySQL Shell to perform a clean switchover.&lt;/p>
&lt;p>Because both clusters are available, the operator can make sure the replica has caught up before changing roles. After the switchover completes, &lt;code>dc2&lt;/code> becomes the writable primary and &lt;code>dc1&lt;/code> becomes a read-only replica.&lt;/p>
&lt;h3 id="emergency-failover">Emergency Failover&lt;/h3>
&lt;p>An emergency failover can be used when the primary cluster is unreachable and a clean handover is no longer possible.&lt;/p>
&lt;p>This is the disaster recovery case: the Kubernetes cluster, region, or network path to the primary may be down, and you need to promote a surviving replica so the application can resume writes.&lt;/p>
&lt;p>To fail over to &lt;code>dc2&lt;/code>, update primaryCluster and explicitly set the forced failover flag:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl patch ps-clusterset my-cluster-set --type&lt;span class="o">=&lt;/span>merge &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p &lt;span class="s1">'{"spec":{"primaryCluster":"dc2","unsafeFlags":{"forcedFailover":true}}}'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The operator only follows this path when it can confirm that the current primary cluster is unreachable. It then promotes &lt;code>dc2&lt;/code>, allowing it to accept writes.&lt;/p>
&lt;p>The explicit flag is important because failover can cause data loss. Replication between clusters is asynchronous, so any writes that reached the old primary but had not yet replicated to &lt;code>dc2&lt;/code> are not present on the new primary. Once &lt;code>dc2&lt;/code> is promoted, those missing writes become unrecoverable through normal ClusterSet recovery.&lt;/p>
&lt;p>The risk of data loss is why the field is named &lt;code>unsafeFlags.forcedFailover&lt;/code>.&lt;/p>
&lt;p>Another important point is that when the old primary comes back, it does not automatically resume as primary. After a forced failover, the recovered cluster must be explicitly reintroduced into the ClusterSet as a replica.&lt;/p>
&lt;h3 id="adding-and-removing-clusters">Adding and removing clusters&lt;/h3>
&lt;p>Adding or removing clusters follows the same declarative pattern: update the custom resource spec and let the operator reconcile the difference.&lt;/p>
&lt;p>To add another replica cluster, add a new entry under clusters:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQLClusterSet&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my-cluster-set&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># .. existing spec&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># .. existing clusters&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">innodbClusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dc3-mysql-primary.default.svc.cluster.local&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The operator joins the new cluster in the same way it joined &lt;code>dc2&lt;/code>: it clones data from the primary, configures replication, and brings the cluster into the ClusterSet as a read-only replica.&lt;/p>
&lt;p>To remove a cluster, delete its entry from the clusters list. You can update your manifest and reapply it, or use a JSON patch:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl patch ps-clusterset my-cluster-set --type&lt;span class="o">=&lt;/span>json &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p &lt;span class="s1">'[{"op":"remove","path":"/spec/clusters/1"}]'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If the cluster is healthy, the operator detaches it cleanly and it becomes a normal standalone cluster again.&lt;/p>
&lt;p>If the cluster being removed is unreachable, you can force its removal:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl patch ps-clusterset my-cluster-set --type&lt;span class="o">=&lt;/span>json -p &lt;span class="s1">'[
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> {"op":"remove","path":"/spec/clusters/1"},
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> {"op":"add","path":"/spec/unsafeFlags/forcedClusterRemoval","value":true}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">]'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Like forced failover, forced removal is gated behind an unsafe flag because the operator should not make this decision silently. Removing an unreachable cluster from a ClusterSet is an operational decision with consequences, and it should be made explicitly.&lt;/p>
&lt;h3 id="wrapping-up">Wrapping up&lt;/h3>
&lt;p>The Percona Operator for MySQL allows extending Group Replication beyond a single site by managing InnoDB ClusterSet through a custom resource &lt;code>PerconaServerMySQLClusterSet&lt;/code>. A primary cluster handles writes, replica clusters stay synchronized, and the operator manages switchovers, failovers, and membership changes declaratively.&lt;/p>
&lt;p>For planned maintenance, switchover moves the primary role safely with no data loss. For outages, forced failover promotes a surviving replica, with the expected risk of losing any writes that had not yet replicated. That replication lag is the practical RPO, so it should be monitored and tested as part of the DR plan.&lt;/p>
&lt;p>With the Percona Operator for MySQL, disaster recovery becomes repeatable, Kubernetes-native, and easier to operate across regions or clusters.&lt;/p>
&lt;h3 id="further-reading">Further reading&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset.html" target="_blank" rel="noopener noreferrer">InnoDB ClusterSet docs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-mysql/latest/replication.html" target="_blank" rel="noopener noreferrer">Cross-site replication in Percona Operator for MySQL&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Mayank Shah</author><category>Percona</category><category>Kubernetes</category><category>Cloud</category><category>Community</category><category>Open Source</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2026/06/mayank-cross-cluster-ps-cover_hu_aacd33ec59babfc4.jpg"/><media:content url="https://percona.community/blog/2026/06/mayank-cross-cluster-ps-cover_hu_65f571b257f55052.jpg" medium="image"/></item><item><title>PostgreSQL Autovacuum Internals and Benchmark</title><link>https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/</link><guid>https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/</guid><pubDate>Wed, 01 Jul 2026 11:00:00 UTC</pubDate><description>Vacuum, or more precisely autovacuum, is the most important automatic maintenance task in PostgreSQL. It is key for performance, but also for long-term database survival. If it runs too often, it can damage performance. If it does not run often enough, performance can suffer. With too few workers, it takes too long. With too many, it consumes resources. If the maintenance work memory is not enough, the load can multiply due to multiple index scans. If you disable it completely, it will rise from the dead and run without limits.</description><content:encoded>&lt;p>Vacuum, or more precisely autovacuum, is the most important automatic maintenance task in PostgreSQL. It is key for performance, but also for long-term database survival. If it runs too often, it can damage performance. If it does not run often enough, performance can suffer. With too few workers, it takes too long. With too many, it consumes resources. If the maintenance work memory is not enough, the load can multiply due to multiple index scans. If you disable it completely, it will rise from the dead and run without limits.&lt;/p>
&lt;p>I guess you get it. It is critical to understand what autovacuum does and how it does it.&lt;/p>
&lt;p>Autovacuum is triggered when certain row count thresholds are crossed. In the final part of this post we describe a benchmark we run to validate if modified rows is the right approach to trigger automatic vacuum execution or we should consider something different like page based thresholds. We will also measure the impact index in vacuum.&lt;/p>
&lt;p>This blog post explains how autovacuum works, but some previous basic understanding of PostgreSQL internals is required.&lt;/p>
&lt;p>Here are the terms you’ll need, feel free to skip if you already know them:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MVCC (multi-version concurrency control)&lt;/strong>: Rather than overwrite a row, PostgreSQL keeps multiple versions of it. This is used to provide consistent views to the different transactions running at the same time, which is why obsolete row versions pile up when there are long running transactions and/or tables are not vacuumed. MVCC is used by transactions to determine row visibility.&lt;/li>
&lt;li>&lt;strong>Tuple&lt;/strong>: One on-disk version of a row. A row updated three times leaves behind three tuples.&lt;/li>
&lt;li>&lt;strong>Dead tuple&lt;/strong>: A tuple that is not visible to any transaction. Reclaiming these is the vacuum’s main job.&lt;/li>
&lt;li>&lt;strong>Heap&lt;/strong>: A table’s main structure, where the tuples live. Indexes are separate structures.&lt;/li>
&lt;li>&lt;strong>Page (block)&lt;/strong>: The 8 KB unit PostgreSQL reads and writes. A heap is an array of pages. If a page is dirty, it means it contains data that hasn’t been written to disk yet.&lt;/li>
&lt;li>&lt;strong>TID&lt;/strong>: A tuple’s address: which page, which slot. Inside of the pages there is an array that points to the actual row position in the page, the slot is the position in that array. This way row space inside of the page can be reorganized without changing the TID. Index entries are TIDs pointing into the heap.&lt;/li>
&lt;li>&lt;strong>Vacuum&lt;/strong>: The operation that removes dead tuples (and does a few things more).&lt;/li>
&lt;li>&lt;strong>Autovacuum&lt;/strong>: Vacuum that PostgreSQL runs for you, in the background, on its own schedule.&lt;/li>
&lt;li>&lt;strong>Visibility map (VM)&lt;/strong>: A small per-table bitmap flagging which pages have all tuples visible or frozen (see freezing).&lt;/li>
&lt;li>&lt;strong>Freezing&lt;/strong>: Stamping old tuples as permanently visible, so their transaction IDs are no longer relevant (see Transaction ID wraparound). Here permanent is a bit misleading, if the row is modified, the permanent tuple will become a dead tuple and will be removed by vacuum.&lt;/li>
&lt;li>&lt;strong>Transaction ID (XID) wraparound&lt;/strong>: Each transaction is assigned an ID. This ID identifies which transactions modified which rows and is thus critical for visibility. The problem is that the transaction counter is finite and eventually wraps around. To avoid problems, older rows must be marked as permanently visible (frozen), this way their transaction id becomes irrelevant.&lt;/li>
&lt;li>&lt;strong>Bloat&lt;/strong>: Space allocated by dead tuples, as dead tuples are not visible, it is wasted space. Vacuum works to reduce it, but can’t always reverse it.&lt;/li>
&lt;li>&lt;strong>Shared buffers&lt;/strong>: PostgreSQL’s in-memory page cache. Nearly all reads and writes pass through it.&lt;/li>
&lt;li>&lt;strong>WAL (write-ahead log)&lt;/strong>: Every change is logged here before it touches a data page, so the database can recover after a crash.&lt;/li>
&lt;li>&lt;strong>Checkpoint&lt;/strong>: The point at which modified (“dirty”) pages in shared buffers are written out to the data files.&lt;/li>
&lt;/ul>
&lt;h2 id="launcher--worker-architecture">Launcher &amp; Worker Architecture&lt;/h2>
&lt;p>The &lt;strong>autovacuum launcher&lt;/strong> is a background process that starts autovacuum workers. Its goal is to start one worker per database every &lt;code>autovacuum_naptime&lt;/code> seconds (default: 1 min). With N databases, this means the launcher starts a new worker roughly every &lt;code>autovacuum_naptime / N&lt;/code> seconds, round-robin across databases. But it is not the launcher that starts the workers. It requests the postmaster to fork an &lt;strong>autovacuum worker&lt;/strong> for the chosen database.&lt;/p>
&lt;p>Workers, once spawned, run independently until they finish all eligible tables in their assigned database and then exit. Up to &lt;code>autovacuum_max_workers&lt;/code> (default 3) workers can run concurrently, and there is no restriction on how many of those may be in the same database. If a database has many tables that need vacuuming, it can run multiple concurrent vacuum workers. In this case, workers coordinate to avoid vacuuming the same table.&lt;/p>
&lt;p>A database approximately has a worker assigned every “nap time seconds” or later. A worker is assigned even if there are no tables requiring vacuum.&lt;/p>
&lt;h2 id="table-selection--prioritization">Table Selection &amp; Prioritization&lt;/h2>
&lt;p>This is the process inside a worker:&lt;/p>
&lt;ol>
&lt;li>Scans &lt;code>pg_class&lt;/code> to enumerate all tables in the database, then fetches per-relation statistics (dead tuple counts, etc.) from the cumulative statistics system (pgstat) for each one.&lt;/li>
&lt;li>Compares each table’s dead-tuple count against the vacuum threshold (see the formula below).&lt;/li>
&lt;li>Also checks if the table needs an ANALYZE (separate threshold).&lt;/li>
&lt;li>Also checks for &lt;strong>anti-wraparound&lt;/strong>: if &lt;code>pg_class.relfrozenxid&lt;/code> age exceeds &lt;code>autovacuum_freeze_max_age&lt;/code> (default 200M transactions), or if &lt;code>pg_class.relminmxid&lt;/code> age exceeds &lt;code>autovacuum_multixact_freeze_max_age&lt;/code> (default 400M), the table is vacuumed regardless of all thresholds and even &lt;code>autovacuum_enabled = off&lt;/code> on the table.&lt;/li>
&lt;/ol>
&lt;h3 id="prioritization">Prioritization&lt;/h3>
&lt;p>There is no table-level priority sorting in a database. A worker vacuums tables in the order they are collected from the &lt;code>pg_class&lt;/code> scan. &lt;code>do_autovacuum()&lt;/code> in &lt;code>src/backend/postmaster/autovacuum.c&lt;/code> iterates the &lt;code>table_oids&lt;/code> list directly. The worker claims each table sequentially by marking it as “mine” in shared memory (this triggers a brief lock to the memory structure so two workers don’t pick the same table at the same time), and calls &lt;code>table_recheck_autovac()&lt;/code> to re-read catalog/pgstat and confirm the table still needs work (as it could have been already vacuumed by another worker). Anti-wraparound urgency is handled one level up, at database selection: the launcher’s &lt;code>do_start_worker()&lt;/code> preferentially dispatches a worker to whichever database is closest to the wraparound limit. So there is no dead-tuple-count-based ordering of tables. Within a database, processing order is effectively catalog order.&lt;/p>
&lt;h2 id="how-vacuum-finds-pages-to-process">How Vacuum Finds Pages to Process&lt;/h2>
&lt;p>Once a table is selected, the worker doesn’t blindly scan every page. It uses the &lt;strong>visibility map&lt;/strong> (VM) to skip pages that do not need vacuuming.&lt;/p>
&lt;h3 id="the-visibility-map">The Visibility Map&lt;/h3>
&lt;p>Every table has an associated visibility map, a bitmap with two bits per heap page:&lt;/p>
&lt;ol>
&lt;li>All-visible bit: every tuple on the page is visible to all current and future transactions. During a normal (non-aggressive) vacuum, this page can generally be skipped. There are no dead tuples to reclaim. However, even all-visible pages may be visited in some cases, such as for eager freezing or readahead optimization (pages are read sequentially even if some of them are not needed).&lt;/li>
&lt;li>All-frozen bit: every tuple on the page is frozen, marked with the &lt;code>HEAP_XMIN_FROZEN&lt;/code> infomask bits (since PostgreSQL 9.4, the value of &lt;code>xmin&lt;/code> is &lt;strong>preserved&lt;/strong> for forensics rather than physically overwritten with &lt;code>FrozenTransactionId&lt;/code> although a lot of people still think the xmin is changed). The page can be skipped even during aggressive/anti-wraparound vacuum. An aggressive vacuum must visit all pages that are &lt;em>not&lt;/em> all-frozen to freeze as many tuples as possible.&lt;/li>
&lt;/ol>
&lt;p>The VM makes vacuuming efficient because it reduces the number of pages to visit while searching for dead tuples. If a 10GB table has dead tuples on only 50 pages, vacuum reads the VM (around 320KB for a 10GB heap, 2 bits per 8KB page) and then focuses on those 50 pages rather than the full 10GB. We already mentioned that a normal vacuum can also visit some additional pages for eager freezing or readahead, but the VM still eliminates the vast majority of random I/O.&lt;/p>
&lt;p>Visibility-map bits are cleared by backends running DML statements and usually set by autovacuum workers or manually triggered vacuum operations.&lt;/p>
&lt;p>The VM is also used by index-only scans to determine whether visiting the heap page to validate tuple visibility is needed. If the page in the VM is marked as “all-visible,” then visibility checks are not required and we don’t need that extra access, improving performance significantly.&lt;/p>
&lt;h3 id="the-scan-process">The Scan Process&lt;/h3>
&lt;p>The worker performs a &lt;strong>sequential scan of the heap&lt;/strong>, but guided by the VM:&lt;/p>
&lt;ol>
&lt;li>Read the VM to identify pages that are NOT all-visible and may contain dead tuples.&lt;/li>
&lt;li>For each such page, read it into shared buffers (if not already there).&lt;/li>
&lt;li>Examine each tuple’s header (&lt;code>t_xmin&lt;/code>, &lt;code>t_xmax&lt;/code>, &lt;code>t_infomask&lt;/code>) to determine if the tuple is dead, meaning it was deleted or updated, and no running transaction can see it anymore.&lt;/li>
&lt;li>Dead tuples are collected into an in-memory &lt;strong>dead-TID store&lt;/strong>, since PG17 a &lt;code>TidStore&lt;/code>, a compact adaptive-radix-tree keyed by block number that replaced the old sorted &lt;code>ItemPointer&lt;/code> array and its hard 1 GB cap. Its size is bounded by &lt;code>autovacuum_work_mem&lt;/code> (default -1, which falls back to &lt;code>maintenance_work_mem&lt;/code>, default 64MB). For manual &lt;code>VACUUM&lt;/code>, &lt;code>maintenance_work_mem&lt;/code> is used directly.&lt;/li>
&lt;li>If the work memory fills up before the table is fully scanned, the worker pauses the heap scan, processes the accumulated dead tuples (index cleanup + heap cleanup), then resumes the heap scan from where it stopped. This means a single vacuum of a large, heavily updated table may involve multiple passes through the indexes.&lt;/li>
&lt;/ol>
&lt;h3 id="limiting-cache-impact-with-the-buffer-ring">Limiting Cache Impact with the Buffer Ring&lt;/h3>
&lt;p>The step 2 above says “read the heap page into shared buffers”), but if vacuum has to pull every page it scans into &lt;code>shared_buffers&lt;/code>, vacuuming a large table would evict the pages that other queries depend on, trashing the cache during a maintenance task. PostgreSQL prevents this with a &lt;strong>buffer access strategy&lt;/strong>, commonly called a &lt;strong>ring buffer&lt;/strong>.&lt;/p>
&lt;p>Rather than allocating pages all over the shared pool, vacuum uses a small &lt;strong>ring&lt;/strong> of shared pool pages that it reuses circularly: when it needs a buffer for a new page, and the ring is full, it recycles the oldest buffer in the ring instead of claiming another from &lt;code>shared_buffers&lt;/code>. If vacuum needs a page already in the shared pool, that page is not added to the ring. The ring size is set by &lt;code>vacuum_buffer_usage_limit&lt;/code>, default 2 MB in PG18 (256 buffers of 8 KB), ranges from 128 kB to 16 GB, with a limit of 1/8 of &lt;code>shared_buffers&lt;/code> (you can set it higher, but it will limited to that value). A value of &lt;code>0&lt;/code> disables the ring entirely, letting vacuum use as much of &lt;code>shared_buffers&lt;/code> as it needs. The same limit applies to &lt;code>ANALYZE&lt;/code> and to autovacuum (which runs the same vacuum code). The &lt;code>VACUUM&lt;/code> command accepts a per-statement &lt;code>BUFFER_USAGE_LIMIT&lt;/code> option.&lt;/p>
&lt;p>The ring has consequences: &lt;strong>When the buffer being recycled is still dirty, vacuum must write it out before reusing the slot&lt;/strong>. As WAL is written before the page, we have to flush any outstanding WAL for that page first. So once vacuum dirties more pages than the ring can hold, it begins doing &lt;strong>its own writes&lt;/strong> inline rather than leaving them all for the checkpointer or background writer. This may look as a trade-off as the ring caps vacuum’s cache footprint, but requires vacuum to perform some of its own write-back (and WAL flushing) as it runs. But if a page was read into the ring, probably that pages was not very active and will not be read again soon. Raising &lt;code>vacuum_buffer_usage_limit&lt;/code> (or setting it to &lt;code>0&lt;/code>) relaxes the limit: a faster vacuum. But a faster vacuum that will evict more active pages and later will require more work by the checkpointer.&lt;/p>
&lt;h3 id="determining-tuple-liveness">Determining Tuple Liveness&lt;/h3>
&lt;p>For each tuple on a non-all-visible page, vacuum checks:&lt;/p>
&lt;ul>
&lt;li>&lt;code>t_xmin&lt;/code> (inserting transaction identified): Check whether it committed. If the inserting transaction aborted, the tuple is dead immediately.&lt;/li>
&lt;li>&lt;code>t_xmax&lt;/code> (deleting/updating transaction identifier): Check whether it committed and is older than the oldest running transaction (&lt;code>OldestXmin&lt;/code>). If so, no active transaction can see this tuple version and the tuple is dead.&lt;/li>
&lt;li>Vacuum, like the other backends, reads &lt;code>pg_xact&lt;/code> (the commit log / CLOG) to determine transaction commit status, and sets &lt;strong>hint bits&lt;/strong> (&lt;code>HEAP_XMIN_COMMITTED&lt;/code>, &lt;code>HEAP_XMAX_COMMITTED&lt;/code>, etc.) on tuple headers so future accesses don’t need to re-check &lt;code>pg_xact&lt;/code>. Changing the hint bits marks the page as dirty, but does not save that change in the WAL, unless specified in the configuration (checksums enabled or wal_log_hints). The purpose of the hint bits is help future transactions know the outcome of the inserting/modifying transactions without checking the commit log.&lt;/li>
&lt;/ul>
&lt;p>&lt;code>OldestXmin&lt;/code> is the oldest transaction ID that any running transaction might still need to see, also known sometimes as the xmin horizon. Tuples deleted or replaced by transactions newer than &lt;code>OldestXmin&lt;/code> &lt;strong>cannot be vacuumed&lt;/strong> because some active transactions might still need them. This is why long-running transactions limit the space that vacuum can reclaim.&lt;/p>
&lt;h2 id="what-happens-to-heap-pages">What Happens to Heap Pages&lt;/h2>
&lt;p>Once dead tuples are identified on a page:&lt;/p>
&lt;ol>
&lt;li>Dead tuple line pointers are set to &lt;code>LP_DEAD&lt;/code> during the heap scan phase. Later, after index cleanup removes all dangling index references, vacuum performs a second heap pass that converts these to &lt;code>LP_UNUSED&lt;/code>, making the slots available for reuse. (For tables with no indexes, vacuum can mark &lt;code>LP_UNUSED&lt;/code> immediately since there are no index pointers to worry about.)&lt;/li>
&lt;li>The page is compacted. Live tuples are shuffled toward the high end of the page, and free space is consolidated in the middle (between the line pointer array and the tuple data area). This is called &lt;strong>page pruning/defragmentation&lt;/strong>. It updates the page’s &lt;code>pd_lower&lt;/code> (end-of-line pointers) and &lt;code>pd_upper&lt;/code> (start-of-tuple data) to reflect the new free space.&lt;/li>
&lt;li>The page is marked dirty in shared buffers. It will be written back to disk by the background writer, at the next checkpoint or if the ring buffer is full and vacuum needs that space. This is the &lt;code>vacuum_cost_page_dirty&lt;/code> cost event (adds 20 to the cost global cost of running vacuum operations).&lt;/li>
&lt;li>The VM is updated. If, after removing dead tuples, every remaining tuple on the page is visible to all transactions, the all-visible bit is set. During aggressive/anti-wraparound vacuum, if all tuples are also frozen, the all-frozen bit is set.&lt;/li>
&lt;li>The FSM (Free Space Map) tree is updated periodically (every &lt;code>VACUUM_FSM_EVERY_PAGES&lt;/code> pages or after heap/index cleanup pass, not after every individual page is added to the FSM) to advertise newly available space, so future DML operations can reuse it.&lt;/li>
&lt;/ol>
&lt;h3 id="heap-truncation">Heap Truncation&lt;/h3>
&lt;p>After processing all pages, vacuum checks whether the &lt;strong>last pages&lt;/strong> of the heap file are entirely empty (all dead tuples were removed and there are no live tuples). If so, it &lt;strong>truncates the file&lt;/strong>, physically shrinking it and returning disk space to the OS. This is the only situation where vacuum reduces the on-disk size of a table. The space reclaimed in the middle of the file is reused, not returned to the filesystem.&lt;/p>
&lt;p>Truncation requires an &lt;strong>AccessExclusiveLock&lt;/strong> lock during the truncation, which can cause a short stall on concurrent access. Table truncation can be disabled per-table or globally with &lt;code>vacuum_truncate = off&lt;/code>.&lt;/p>
&lt;h2 id="index-cleanup">Index Cleanup&lt;/h2>
&lt;p>Index cleanup is also required and is often an expensive part of vacuum. Indexes must be cleaned because they contain pointers (TIDs) to heap tuples. If the corresponding heap tuple is dead, the index entry becomes a &lt;strong>dangling pointer&lt;/strong> and must be removed.&lt;/p>
&lt;h3 id="the-process">The Process&lt;/h3>
&lt;ol>
&lt;li>After the heap scan (or after &lt;code>maintenance_work_mem&lt;/code> fills), vacuum has its dead-TID store populated (block-ordered).&lt;/li>
&lt;li>For &lt;strong>each index&lt;/strong> on the table, vacuum calls the index access method’s &lt;code>ambulkdelete&lt;/code> function. For B-tree indexes, this invokes &lt;code>btbulkdelete()&lt;/code> then &lt;code>btvacuumscan()&lt;/code>, which scans the &lt;strong>entire index in physical order&lt;/strong> (every page except the metapage, including all leaf pages), checking every index entry’s TID against the dead-TID store. Matching entries are removed. (See &lt;code>btvacuumscan()&lt;/code> in &lt;code>src/backend/access/nbtree/nbtree.c&lt;/code>, which processes each page with &lt;code>btvacuumpage()&lt;/code>.)&lt;/li>
&lt;li>Only &lt;strong>after&lt;/strong> all indexes are cleaned does vacuum go back and clean the heap pages (mark &lt;code>LP_UNUSED&lt;/code>, compact). This is handled by &lt;code>lazy_vacuum_all_indexes()&lt;/code> followed by the heap cleanup phase in &lt;a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/heap/vacuumlazy.c" target="_blank" rel="noopener noreferrer">&lt;code>src/backend/access/heap/vacuumlazy.c&lt;/code>&lt;/a>.&lt;/li>
&lt;/ol>
&lt;p>The order of operations is important: index entries must be removed &lt;strong>before&lt;/strong> their heap tuple slots are recycled, otherwise an index scan could follow a pointer to a slot that now holds a different, unrelated tuple, returning incorrect results.&lt;/p>
&lt;h3 id="why-index-vacuum-is-expensive">Why Index Vacuum Is Expensive&lt;/h3>
&lt;p>The PostgreSQL documentation for the &lt;a href="https://www.postgresql.org/docs/18/index-functions.html" target="_blank" rel="noopener noreferrer">&lt;code>ambulkdelete&lt;/code> interface&lt;/a> states:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>This is a “bulk delete” operation that is intended to be implemented by &lt;strong>scanning the whole index&lt;/strong> and checking each entry to see if it should be deleted.&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>There is &lt;strong>no partial index scan optimization&lt;/strong>. The design requires scanning the index completely. The dead TIDs are sorted by heap location, but index entries are ordered by key value, so there is no way to locate only the affected index pages without scanning all leaf pages.&lt;/p>
&lt;p>Consequences:&lt;/p>
&lt;ul>
&lt;li>Each index is completely scanned for every vacuum cycle. For a table with 5 indexes and 100GB of index data, vacuum reads 500GB of index pages each full pass.&lt;/li>
&lt;li>If the space for dead-TID is too small and the heap scan must pause mid-way, &lt;code>ambulkdelete&lt;/code> is called &lt;strong>multiple times&lt;/strong>, once per batch of dead TIDs. The documentation states: “Because of limited &lt;code>maintenance_work_mem&lt;/code>, &lt;code>ambulkdelete&lt;/code> might need to be called more than once when many tuples are to be deleted.” Each call performs a full index scan. With a 100GB table, 64MB of work memory, and 5 indexes, this can result in dozens of full index scans. (PG17’s &lt;code>TidStore&lt;/code> packs far more dead TIDs into the same memory, so this multi-pass case is much rarer than it was in previous versions.)&lt;/li>
&lt;li>This is why increasing &lt;code>autovacuum_work_mem&lt;/code> (or &lt;code>maintenance_work_mem&lt;/code>) for vacuum-heavy workloads can be required. If the autovacuum operation is written into the log (&lt;code>log_autovacuum_min_duration&lt;/code>), look for &lt;code>index scans&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="index-cleanup-optimizations">Index Cleanup Optimizations&lt;/h3>
&lt;ul>
&lt;li>Bypass optimization (near-zero dead tuples): when 2% or fewer of the table’s pages contain &lt;code>LP_DEAD&lt;/code> items and the accumulated dead-TID storage stays under 32MB, vacuum enters bypass mode: it skips both index cleanup and the second heap-vacuuming pass, avoiding a full index scan as the benefit is reduced. This avoids the jump between “zero dead tuples is instant” and “one dead tuple requires multiple full index scans”. (See &lt;code>BYPASS_THRESHOLD_PAGES&lt;/code> in &lt;code>vacuumlazy.c&lt;/code>.)&lt;/li>
&lt;li>&lt;code>INDEX_CLEANUP&lt;/code> parameter: &lt;code>AUTO&lt;/code> (default) allows the bypass optimization; &lt;code>OFF&lt;/code> forces vacuum to always skip index vacuuming (accepting index bloat); &lt;code>ON&lt;/code> forces full index vacuuming every time. The &lt;code>OFF&lt;/code> setting is useful for emergency situations where you need vacuum to advance &lt;code>relfrozenxid&lt;/code> quickly. (See &lt;a href="https://www.postgresql.org/docs/18/sql-vacuum.html" target="_blank" rel="noopener noreferrer">VACUUM documentation&lt;/a>.)&lt;/li>
&lt;li>B-tree “page deletion”: when a B-tree leaf page becomes empty after vacuum removes all its entries, the page is marked as deleted and can be recycled. The file does not reduce its size, but the pages can be reused later.&lt;/li>
&lt;li>Simple B-tree tuple deletion: when a query visits a dead tuple via an index scan, it can mark that pointer as dead in the index itself. If, at a later time, more space is needed in that page, instead of performing a split, the index entries pointing to dead tuples can be removed to make room for the new entry.&lt;/li>
&lt;li>Bottom-up deletion (PG14+): B-tree indexes can proactively remove known-dead entries during page splits, reducing the work left for vacuum.&lt;/li>
&lt;/ul>
&lt;h2 id="concurrency-vacuum-vs-active-backends">Concurrency: Vacuum vs. Active Backends&lt;/h2>
&lt;p>Vacuum runs concurrently with normal database operations. It does &lt;strong>not&lt;/strong> lock the table exclusively (it takes a &lt;code>ShareUpdateExclusiveLock&lt;/code>, which conflicts only with other vacuums, &lt;code>ALTER TABLE&lt;/code>, and certain &lt;code>CREATE INDEX&lt;/code> operations).&lt;/p>
&lt;h3 id="page-level-locking">Page-Level Locking&lt;/h3>
&lt;p>When vacuum needs to read or modify a heap page it uses the common shared buffer access locks:&lt;/p>
&lt;ol>
&lt;li>For reading (AKA identifying dead tuples), vacuum acquires a shared &lt;strong>buffer content lock&lt;/strong> on the shared buffer.&lt;/li>
&lt;li>For pruning and freezing (AKA removing dead tuples, setting vm flags), vacuum requires a &lt;strong>buffer cleanup lock&lt;/strong>. This is an exclusive lock (no other backend can hold a lock on the buffer). In a non-aggressive vacuum, if the cleanup lock cannot be obtained immediately (another transaction has a shared lock for example), vacuum &lt;strong>skips pruning/freezing on that page&lt;/strong> and moves on. An aggressive (anti-wraparound) vacuum will wait for the lock instead.&lt;/li>
&lt;li>These locks are held only for the duration of the in-memory page operation and should be very fast. They do &lt;strong>not&lt;/strong> block concurrent &lt;code>SELECT&lt;/code> or &lt;code>DML&lt;/code> on other pages.&lt;/li>
&lt;/ol>
&lt;h3 id="what-happens-when-a-backend-reads-a-page-being-vacuumed">What Happens When a Backend Reads a Page Being Vacuumed&lt;/h3>
&lt;ul>
&lt;li>If vacuum is &lt;strong>currently modifying&lt;/strong> the page (holding the cleanup lock): the backend waits until vacuum releases the lock, then reads the page in its post-vacuum state. The backend sees only live tuples. The dead ones have just been removed. This is safe because the dead tuples were invisible to the backend’s snapshot anyway.&lt;/li>
&lt;li>If vacuum &lt;strong>skipped&lt;/strong> the page (could not get the cleanup lock): dead tuples remain on the page. They are invisible to backends via MVCC visibility checks and will be cleaned up in a future vacuum cycle.&lt;/li>
&lt;li>If vacuum has &lt;strong>not yet reached&lt;/strong> the page: the backend reads normally. Dead tuples are still present but invisible to the backend’s MVCC snapshot. They are skipped during visibility checks.&lt;/li>
&lt;/ul>
&lt;h3 id="what-happens-when-a-backend-writes-while-vacuum-runs">What Happens When a Backend Writes While Vacuum Runs&lt;/h3>
&lt;ul>
&lt;li>INSERT into a vacuumed page: vacuum freed space, the FSM knows about it, the inserter uses that space. No conflict.&lt;/li>
&lt;li>UPDATE/DELETE on the same table: concurrent DML does not conflict with vacuum’s &lt;code>ShareUpdateExclusiveLock&lt;/code>. If a backend deletes/updates a tuple on a page vacuum hasn’t reached yet, vacuum will find and clean it (if committed by then). If the tuple is on a page vacuum already passed, it will be caught by the next vacuum cycle.&lt;/li>
&lt;li>UPDATE/DELETE on a page vacuum is currently processing: the buffer lock serializes access. If vacuum removes dead tuples and the backend then updates a live tuple on the same page, there’s no conflict because they operate on different tuple slots.&lt;/li>
&lt;/ul>
&lt;h3 id="index-scan-during-index-cleanup">Index Scan During Index Cleanup&lt;/h3>
&lt;p>While vacuum scans an index to remove dead entries, concurrent index scans by backends can proceed normally. B-tree indexes use a &lt;strong>pin-based&lt;/strong> protocol that avoids vacuum deleting a page that any backend has pinned. Specifically, vacuum marks pages as half-dead first, and only recycles them when no backend holds a pin. This ensures index scans never follow a pointer to a recycled page.&lt;/p>
&lt;h2 id="threshold-formula">Threshold Formula&lt;/h2>
&lt;p>A table becomes eligible for autovacuum when its dead-tuple count crosses a threshold. As of PG18 the calculation is limited by &lt;code>autovacuum_vacuum_max_threshold&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">vacuum_threshold = Min(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> autovacuum_vacuum_max_threshold,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * reltuples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Defaults:&lt;/p>
&lt;ul>
&lt;li>&lt;code>autovacuum_vacuum_threshold = 50&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_scale_factor = 0.2&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_max_threshold = 100,000,000&lt;/code> (&lt;strong>new in PG18&lt;/strong>).&lt;/li>
&lt;/ul>
&lt;p>&lt;code>autovacuum_vacuum_max_threshold&lt;/code> is used to avoid massive tables requiring a huge number of dead tuples before firing vacuum.&lt;/p>
&lt;h3 id="insert-triggered-vacuum-pg13">Insert-triggered vacuum (PG13+)&lt;/h3>
&lt;p>A table also becomes eligible based on inserts alone. Since PG18 the scale-factor term is multiplied by the &lt;strong>unfrozen fraction&lt;/strong> of the table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">vacuum_insert_threshold =
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> autovacuum_vacuum_insert_threshold
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + autovacuum_vacuum_insert_scale_factor * reltuples * (1 - relallfrozen / relpages)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Defaults:&lt;/p>
&lt;ul>
&lt;li>&lt;code>autovacuum_vacuum_insert_threshold = 1000&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_insert_scale_factor = 0.2&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>(1 - relallfrozen / relpages)&lt;/code> is used to avoid time between runs constantly growing for tables that are mostly inserted. In previous versions, as the table grows the number of inserted rows required to trigger a vacuum used to grow also. With this optimization, the number of inserts required to trigger vacuum tends to be more constant.&lt;/p>
&lt;h3 id="analyze-trigger">ANALYZE trigger&lt;/h3>
&lt;p>The following formula applies to determine if ANALYZE is required:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">changed_tuples > analyze_threshold + analyze_scale_factor * reltuples&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Defaults: &lt;code>autovacuum_analyze_threshold = 50&lt;/code>, &lt;code>autovacuum_analyze_scale_factor = 0.1&lt;/code>.&lt;/p>
&lt;p>Per-table overrides via &lt;code>ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = ...)&lt;/code> that take precedence over default configuration.&lt;/p>
&lt;h2 id="cost-based-vacuum-throttling">Cost-Based Vacuum Throttling&lt;/h2>
&lt;p>The cost-based mechanism is linked directly to the page-level operations described above. Every time vacuum touches a page, it incurs a cost depending on what happened:&lt;/p>
&lt;p>Vacuum I/O is throttled via a cost/delay mechanism shared with manual &lt;code>VACUUM&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>vacuum_cost_page_hit&lt;/code> = 1, page already in shared buffers (cheap: no I/O, only CPU to inspect tuples)&lt;/li>
&lt;li>&lt;code>vacuum_cost_page_miss&lt;/code> = 10 (PG17 and earlier; &lt;strong>changed to 2 in PG18&lt;/strong>), page read from OS into shared buffers (may still be in OS page cache, so not necessarily a physical disk read)&lt;/li>
&lt;li>&lt;code>vacuum_cost_page_dirty&lt;/code> = 20, vacuum modified the page (removed dead tuples, compacted it). This is the most expensive because it generates a dirty buffer that must eventually be written to disk by the background writer/checkpointer&lt;/li>
&lt;/ul>
&lt;p>These costs are &lt;strong>additive per page&lt;/strong>. On PG17 (miss = 10): a page read from disk and then modified costs &lt;strong>30&lt;/strong>. On PG18 (miss = 2): the same scenario costs &lt;strong>22&lt;/strong>. A page already in shared buffers (hit = 1) that gets modified costs &lt;strong>21&lt;/strong> on both versions.&lt;/p>
&lt;p>The cost limit is &lt;strong>shared across all running autovacuum workers&lt;/strong>. If 3 workers are active, each effectively gets &lt;code>200 / 3&lt;/code> or around &lt;code>66&lt;/code> cost budget per cycle. This means adding more workers doesn’t linearly increase I/O as all workers get their cost limit reduced. The global limit is &lt;code>autovacuum_vacuum_cost_limit&lt;/code>, that by default is -1, meaning it inherits &lt;code>vacuum_cost_limit&lt;/code>, which by default is 200.&lt;/p>
&lt;p>The workers accumulate cost points as operations happen. When, for a specific worker, the accumulated total reaches its assigned limit, the worker will sleeps for &lt;code>autovacuum_vacuum_cost_delay&lt;/code> (default 2ms).&lt;/p>
&lt;h3 id="example">Example&lt;/h3>
&lt;p>With defaults (limit=200, delay=2ms), one worker on &lt;strong>PG17&lt;/strong> (miss=10):&lt;/p>
&lt;ul>
&lt;li>If all pages are a miss + dirty write (cost 30 each): 200/30 ~ &lt;strong>6 pages&lt;/strong>, then sleep 2ms, giving ~3,000 pages/sec.&lt;/li>
&lt;li>If every page is already in shared buffers and gets modified (hit + dirty = 21): 200/21 ~ &lt;strong>9 pages&lt;/strong>, then sleep 2ms, giving ~4,500 pages/sec.&lt;/li>
&lt;li>If all pages are a shared-buffer hit with no modifications (cost 1 each): 200 pages, then sleep 2ms, giving ~100,000 pages/sec.&lt;/li>
&lt;/ul>
&lt;p>On &lt;strong>PG18&lt;/strong> (miss=2), miss + dirty reduces the cost to 22 per page, so throughput for cold pages rises to 200/22 ~ 9 pages per cycle.&lt;/p>
&lt;p>If we have 3 workers sharing the limit, then each gets 66 cost/cycle, so throughput per worker drops proportionally.&lt;/p>
&lt;p>For large this default is often &lt;strong>too conservative&lt;/strong>. Common tuning: raise &lt;code>autovacuum_vacuum_cost_limit&lt;/code> to 1000-2000 and/or reduce &lt;code>cost_delay&lt;/code> to 0 on critical tables.&lt;/p>
&lt;p>The manual &lt;code>VACUUM&lt;/code> parameter &lt;code>vacuum_cost_delay&lt;/code> defaults to 0 (no throttling). Autovacuum workers use &lt;code>autovacuum_vacuum_cost_delay&lt;/code>, which has the default value of 2ms since PG12 (earlier versions defaulted to 20ms). Per-table storage parameters &lt;code>autovacuum_vacuum_cost_delay&lt;/code> / &lt;code>autovacuum_vacuum_cost_limit&lt;/code> override the globals for that specific table. This a way to tune the impact of high-churn tables on the shared cost.&lt;/p>
&lt;h2 id="what-drives-vacuum-cost">What Drives Vacuum Cost&lt;/h2>
&lt;p>As we’ve seen, autovacuum is triggered by the number of dead or inserted tuples. But is the real cost driven by the number of dead tuples, or by the number of pages it has to visit and clean?.&lt;/p>
&lt;p>We designed a benchmark to try to discover which is the real cost driver for autovacuum operations.&lt;/p>
&lt;h3 id="the-question">The question&lt;/h3>
&lt;p>We have two hypotheses that we want to analyze:&lt;/p>
&lt;ol>
&lt;li>Whether autovacuum cost is driven by the &lt;strong>count of dead tuples&lt;/strong> or by the &lt;strong>number of heap pages&lt;/strong> those dead tuples are spread across.&lt;/li>
&lt;li>How the &lt;strong>number of indexes&lt;/strong> amplifies that cost.&lt;/li>
&lt;/ol>
&lt;h3 id="the-table-and-the-key-variable">The table and the key variable&lt;/h3>
&lt;p>We will use a single table for every run of the benchmark. We will drop and recreate the table each time:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bench_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INTEGER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- sequential 1 .. 10,000,000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">val&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INTEGER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">padding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">TEXT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- repeat('x', 96)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The padding column fixes the row width at 128 bytes, giving around &lt;strong>58 rows per 8 KB page&lt;/strong>. For 10M rows we will have &lt;strong>172,414 heap pages&lt;/strong> or 1.3 GB. We fill the table, then run &lt;code>VACUUM FREEZE&lt;/code> so every page starts &lt;strong>all-visible and all-frozen&lt;/strong> to have a clean baseline. Then we create 0 to 5 &lt;strong>redundant B-tree indexes&lt;/strong>, all on &lt;code>id&lt;/code>. Each index is a separate physical structure that vacuum must scan in full.&lt;/p>
&lt;p>The independent variable here is &lt;em>the distribution of dead tuples&lt;/em>. We use two strategies delete the &lt;strong>same number of rows&lt;/strong>. They differ only in which pages are touched:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Strategy&lt;/th>
&lt;th>DELETE predicate (10%)&lt;/th>
&lt;th>Pages dirtied&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>compact&lt;/strong>&lt;/td>
&lt;td>&lt;code>WHERE id &lt;= 1,000,000&lt;/code>&lt;/td>
&lt;td>first ~10% of pages (low ids = first physical pages)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>spread&lt;/strong>&lt;/td>
&lt;td>&lt;code>WHERE id % 10 = 0&lt;/code>&lt;/td>
&lt;td>100% of pages (every page loses between 5 and 6 rows of its 58 rows)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>For this benchmark, we use &lt;code>DELETE&lt;/code> rather than &lt;code>UPDATE&lt;/code> so no new tuple versions are created. The table does not grow and no index entries are added (no leaf page splits).&lt;/p>
&lt;h3 id="the-test-matrix">The test matrix&lt;/h3>
&lt;p>We have 6 index counts (0-5), multiplied by 3 dead-tuple percentages (10/25/50%) and 2 distributions gives us 36 combinations. We repeated each combination 10 times for a total of 360 runs.&lt;/p>
&lt;h3 id="how-a-single-run-is-measured">How a single run is measured&lt;/h3>
&lt;ol>
&lt;li>Recreate the table, fill it with data, &lt;code>VACUUM FREEZE&lt;/code>, build the required indexes for the test (autovacuum disabled on the table throughout setup).&lt;/li>
&lt;li>&lt;code>DELETE&lt;/code> to generate the dead tuples for this combination.&lt;/li>
&lt;li>Pre-test &lt;code>CHECKPOINT&lt;/code>: flush the buffers dirtied during setup, so the post-test checkpoint will only account for pages autovacuum makes dirty.&lt;/li>
&lt;li>Reset the shared I/O counters: &lt;code>pg_stat_reset_shared('io' | 'bgwriter' | 'checkpointer')&lt;/code>. Note that we do not call &lt;code>pg_stat_reset()&lt;/code>, which would zero &lt;code>n_dead_tup&lt;/code> and prevent autovacuum from triggering.&lt;/li>
&lt;li>Record the start time, then enable autovacuum on the table with parameters that should trigger autovacuum (&lt;code>autovacuum_vacuum_threshold = 1&lt;/code>, &lt;code>autovacuum_vacuum_scale_factor = 0&lt;/code>). As &lt;code>autovacuum_naptime&lt;/code> is 1s, the launcher should pick the table up approximately within a second.&lt;/li>
&lt;li>Poll every 0.5 s until vacuum is done: &lt;code>last_autovacuum&lt;/code> is after start_time&lt;code>and&lt;/code>n_dead_tup = 0`.&lt;/li>
&lt;li>Stop the clock, force a post-test checkpoint, and collect metrics.&lt;/li>
&lt;/ol>
&lt;h3 id="what-is-captured-and-from-where">What is captured, and from where&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Signal&lt;/th>
&lt;th>Source&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Wall-clock &lt;code>duration_s&lt;/code>&lt;/td>
&lt;td>&lt;code>clock_gettime&lt;/code> (monotonic)&lt;/td>
&lt;td>approximate time (vacuum+nap)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Autovacuum-worker reads/hits/writes&lt;/td>
&lt;td>&lt;code>pg_stat_io&lt;/code>, filtered to &lt;code>backend_type = 'autovacuum worker'&lt;/code>&lt;/td>
&lt;td>isolates the worker from every other process (PG18)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Heap vs index blocks&lt;/td>
&lt;td>&lt;code>pg_statio_user_tables&lt;/code> (before/after diff)&lt;/td>
&lt;td>specific table data&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Write breakdown&lt;/td>
&lt;td>&lt;code>pg_stat_io&lt;/code> per backend + &lt;code>pg_stat_checkpointer&lt;/code>&lt;/td>
&lt;td>who wrote the dirty pages&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dirty-page count / completion&lt;/td>
&lt;td>&lt;code>pg_visibility_map_summary&lt;/code> (from &lt;code>pg_visibility&lt;/code>)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I/O is recorded two ways: &lt;strong>operation counts&lt;/strong> (&lt;code>reads&lt;/code>/&lt;code>writes&lt;/code>, which can be multi-block) and &lt;strong>byte-derived page counts&lt;/strong> (&lt;code>read_bytes&lt;/code>/&lt;code>write_bytes&lt;/code> ÷ 8192, exact regardless of multi-block coalescing). We decided to look at the byte-derived counts. Each combination’s 10 iterations are aggregated as a &lt;strong>median with an interquartile (Q1-Q3) band&lt;/strong>.&lt;/p>
&lt;h3 id="results">Results&lt;/h3>
&lt;p>This is the test environment we used:&lt;/p>
&lt;ul>
&lt;li>PostgreSQL: 18.4 (PGDG, &lt;code>pg_visibility&lt;/code> contrib)&lt;/li>
&lt;li>OS / host: Ubuntu 24.04 LTS, x86_64, 4 dedicated vCPU / 15 GiB RAM, SSD-backed&lt;/li>
&lt;li>Execution: benchmark runs locally on the DB host over the Unix socket (no network in the timing path)&lt;/li>
&lt;li>Key GUCs:
&lt;ul>
&lt;li>&lt;code>shared_buffers = 4GB&lt;/code>&lt;/li>
&lt;li>&lt;code>maintenance_work_mem = 1GB&lt;/code>&lt;/li>
&lt;li>&lt;code>work_mem = 64MB&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_naptime = 1s&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_cost_delay = 2ms&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_cost_limit = 200&lt;/code>&lt;/li>
&lt;li>&lt;code>vacuum_cost_page_miss = 2&lt;/code>&lt;/li>
&lt;li>&lt;code>checkpoint_timeout = 15min&lt;/code>&lt;/li>
&lt;li>&lt;code>max_wal_size = 4GB&lt;/code>&lt;/li>
&lt;li>&lt;code>full_page_writes = on&lt;/code>&lt;/li>
&lt;li>&lt;code>track_io_timing = on&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Per-table triggers:
&lt;ul>
&lt;li>&lt;code>autovacuum_vacuum_threshold = 1&lt;/code>&lt;/li>
&lt;li>&lt;code>autovacuum_vacuum_scale_factor = 0&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Workload:
&lt;ul>
&lt;li>Table with 10,000,000 rows (172,414 heap pages, ~1.3 GB)&lt;/li>
&lt;li>2.4 GB working set with 5 indexes (around 225 MB each), fits completely in &lt;code>shared_buffers&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Sampling: 36 combinations x 10 iterations = 360 runs&lt;/li>
&lt;/ul>
&lt;p>The whole 2.4 GB working set (heap plus five 225 MB indexes) is resident in &lt;code>shared_buffers&lt;/code>. The &lt;code>maintenance_work_mem = 1 GB&lt;/code> keeps index cleanup single-pass. Autovacuum throttling is at the default (&lt;code>cost_delay = 2ms&lt;/code>). Durations below are 10-iteration medians.&lt;/p>
&lt;p>Hypothesis 1: it’s pages, not tuples. At 0 indexes:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>dead tuples&lt;/th>
&lt;th style="text-align: right">compact&lt;/th>
&lt;th style="text-align: right">spread&lt;/th>
&lt;th style="text-align: right">spread / compact&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10% (1.0M)&lt;/td>
&lt;td style="text-align: right">5.0 s&lt;/td>
&lt;td style="text-align: right">43.6 s&lt;/td>
&lt;td style="text-align: right">8.7x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>25% (2.5M)&lt;/td>
&lt;td style="text-align: right">11.5 s&lt;/td>
&lt;td style="text-align: right">43.6 s&lt;/td>
&lt;td style="text-align: right">3.8x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50% (5.0M)&lt;/td>
&lt;td style="text-align: right">21.8 s&lt;/td>
&lt;td style="text-align: right">43.4 s&lt;/td>
&lt;td style="text-align: right">2.0x&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>First thing we see is that the &lt;strong>spread column is almost constant&lt;/strong> (43.6, 43.6, 43.4 s) as the dead-tuple count goes from 1M to 5M, because in every case 100% of pages are dirtied. This means that pages visited is the cost driver. Meanwhile the compact column scales because, for compact, more dead tuples means more pages become dirty. When we have 50% dead tuples, the cost is half the spread.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2700" height="900" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_duration_compact_vs_spread_hu_aa90128ac0ca474c.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_duration_compact_vs_spread_hu_c88b323fccf851c3.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_duration_compact_vs_spread_hu_87e198206386000e.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_compact_vs_spread_hu_87e198206386000e.webp" alt="Autovacuum duration, compact vs spread, one panel per dead-tuple percentage. Spread (dashed) sits far above compact (solid) and is nearly flat across 10/25/50%." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The following chart plots every run by &lt;em>dirty-page percentage&lt;/em> rather than dead-tuple count.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1500" height="1050" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_dirty_pages_vs_duration_hu_3ec1033a8558728.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_dirty_pages_vs_duration_hu_75a5742c8bb3bcd.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_dirty_pages_vs_duration_hu_43ee6278e7923d7c.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_dirty_pages_vs_duration_hu_43ee6278e7923d7c.webp" alt="Scatter of autovacuum duration against percentage of pages dirty before vacuum; points rise with dirty-page %, colored by index count." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For the second hypothesis, we see that indexes amplify the cost. Each redundant index adds a near-constant increment (compact 50%): 21.8, 28.1, 32.6, 37.6, 42.3, 47.4 s. Around 5s per index. The number of dead pages also adds some cost, but if the number of dead pages is reduced, then the impact of indexes is lower (compact 10%)&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2100" height="900" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_duration_by_indexes_hu_f0cc89f6aa9637f2.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_duration_by_indexes_hu_8488162358f862f6.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_duration_by_indexes_hu_7db5efa603f147d.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_by_indexes_hu_7db5efa603f147d.webp" alt="Autovacuum duration versus number of indexes, compact and spread panels; each line rises roughly linearly with index count." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The I/O data shows some &lt;strong>distribution-specific&lt;/strong> behaviors. For spread 50%, heap blocks accessed hold at &lt;strong>547,323&lt;/strong> across 1-5 indexes (the heap is scanned once, guided by the VM) while index blocks grow &lt;strong>+27,422 per index&lt;/strong>. Each &lt;code>ambulkdelete&lt;/code> is a full leaf scan. Compact 50% behaves differently: the heap io is lower (&lt;strong>288,699&lt;/strong>, since fewer pages have dead tuples) but index blocks climb &lt;strong>4x faster (~+109,533 per index)&lt;/strong>. Deleting contiguous id ranges empties whole B-tree leaf pages, adding page-deletion and recycling work (B-trees recycle fully-empty pages rather than merging partially-filled ones) on top of the scan. One thing worth noting is that heap access is &lt;strong>not&lt;/strong> flat from 0 to 1 index, it jumps (spread 374,903 to 547,323) because index cleanup forces vacuum’s second heap pass. After that, it is flat only across 1-5 indexes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2100" height="1050" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_heap_vs_index_io_hu_149c2c236218f145.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_heap_vs_index_io_hu_1ba89b82b3ee4b87.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_heap_vs_index_io_hu_71a23ce1a786f235.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_heap_vs_index_io_hu_71a23ce1a786f235.webp" alt="Stacked heap-versus-index blocks accessed by index count; heap roughly constant across 1–5 indexes while index I/O grows linearly, more steeply for compact than spread." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The write breakdown tells us that who writes vacuum’s dirtied pages is not fixed. With zero or one index, the autovacuum worker writes almost nothing: it dirties heap buffers and leaves them for the checkpointer, which flushes all of them in those cases. But as indexes are added, the &lt;strong>worker’s own writes increase&lt;/strong>: spread 50% goes 0, 27k, 54k, 82k, 109k, 136k pages written by the worker for 0-5 indexes (compact 50%: 0, 0, 14k, 27k, 41k, 55k). This is the effect of the &lt;strong>buffer ring&lt;/strong> (see &lt;em>Limiting Cache Impact&lt;/em> above): once index cleanup dirties more pages than the 2 MB ring can hold, the worker has to write and evict them itself rather than defer to the checkpointer. So the write cost shifts from the checkpointer toward the worker as index count grows, a direct consequence of the cache-protecting ring.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2100" height="1050" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_write_breakdown_hu_ad8cc294290e2f51.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_write_breakdown_hu_3ab56907dd6066c.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_write_breakdown_hu_cdcca1229a8a4753.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_write_breakdown_hu_cdcca1229a8a4753.webp" alt="Write breakdown by process; at low index counts the checkpointer does nearly all writes, but the autovacuum worker’s own writes grow with index count." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Finally we have a heat map of the durations across all 36 combinations:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1500" height="900" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/pep_autovacuum_duration_heatmap_hu_29f3771ae86a168e.webp 768w, https://percona.community/blog/2026/06/pep_autovacuum_duration_heatmap_hu_33fea163a4780162.webp 480w, https://percona.community/blog/2026/06/pep_autovacuum_duration_heatmap_hu_3e1a27dd992a347c.webp 1400w" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_heatmap_hu_3e1a27dd992a347c.webp" alt="Heatmap of autovacuum duration for every distribution/dead-percentage/index-count combination." loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>So our conclusion is, as expected, that &lt;strong>dirty pages is the main cost driver for autovacuum, followed by the number of indexes&lt;/strong>. And that we may have the same number of dead rows with completely different autovacuum costs.&lt;/p>
&lt;h3 id="what-this-benchmark-does-and-does-not-show">What this benchmark does and does not show&lt;/h3>
&lt;p>The result is clear, but getting a clear result using a synthetic workload should be read with care. Our benchmark intentionally avoids complexity. And complexity is what makes production vacuum hard.&lt;/p>
&lt;ul>
&lt;li>We built a &lt;code>VACUUM&lt;/code> benchmark wearing autovacuum’s clothes. With one table, &lt;code>threshold = 1&lt;/code>, &lt;code>scale_factor = 0&lt;/code>, and &lt;code>naptime = 1s&lt;/code>, we measure the isolated work of a single worker on one table. It says nothing about the parts that are specific of autovacuum: launcher table-selection, &lt;code>autovacuum_max_workers&lt;/code> contention, or the cost limit shared across workers. The scheduling dynamics are often critical, and they are absent here.&lt;/li>
&lt;li>The workload we use in the benchmark, as usual for a benchmark, is synthetic. Dead tuples come from a single bulk &lt;code>DELETE&lt;/code> on an idle table. There is no concurrency. There are no long-running transaction holding back &lt;code>OldestXmin&lt;/code>. Real vacuum routinely skips pages it can’t get a cleanup lock on (see &lt;em>Concurrency&lt;/em>). In our case, this never happens, which is why every run cleans to 100% all-visible. The benchmark measures vacuum on an idealized table, not production churn.&lt;/li>
&lt;li>Everything fits in memory, so this is not an I/O-bound situation. With the whole table in &lt;code>shared_buffers&lt;/code>, reads are mostly buffer hits, and writes are largely deferred to the checkpointer (the worker itself writes little except index pages at higher index counts). The “cost” being measured is page visits and CPU, plus deferred checkpoint writes. The vacuums that are I/O-bound due to scans of heaps and indexes that don’t fit in cache, are painful. This case is excluded from the benchmark and we may say that the conclusions are about &lt;em>logical&lt;/em> work, rather than IO bound operations. We consider that IO bound operations could show greater differences, but we did not test them.&lt;/li>
&lt;li>Redundant identical indexes don’t generalize. Five B-tree indexes on the integer column is a trick used to make index work scale linearly. Real indexes differ in width, key type, correlation, bloat, fill factor, and bottom-up-deletion behavior. The measured slope (5 s per index; 27,422 index blocks per index for spread, but 109,533 for compact) is specific to this shape (even the slope is distribution-dependent) and can not be directly extrapolated to wide, composite, or text indexes.&lt;/li>
&lt;li>Freezing was excluded. The &lt;code>VACUUM FREEZE&lt;/code> baseline ignored freezing. Anti-wraparound / aggressive vacuums, which must visit every not-all-frozen page and are often the most disruptive events in production, are not part of this benchmark. Besides, the bypass optimization and multi-pass &lt;code>ambulkdelete&lt;/code> (under &lt;code>maintenance_work_mem&lt;/code> pressure) is avoided, and this makes vacuum runtime nonlinear and hard to predict in production.&lt;/li>
&lt;/ul>
&lt;p>Our findings are valid: vacuum cost is driven by dirty pages, not by dead-tuple count, and the mechanism behind it is clear. But the benchmark used a single table that fits entirely in memory, recently frozen, and ran with no concurrent activity. Under those conditions, both runtime and the page-visit counts grew linearly with the number of dirty pages and the number of indexes. It is not clear if these results can be extrapolated to a busy, larger-than-RAM production system. This is left for a future exercise.&lt;/p>
&lt;h2 id="references">References&lt;/h2>
&lt;h3 id="postgresql-documentation">PostgreSQL Documentation&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/index-functions.html" target="_blank" rel="noopener noreferrer">Index Access Method Functions&lt;/a>. Defines the &lt;code>ambulkdelete&lt;/code> and &lt;code>amvacuumcleanup&lt;/code> interfaces; documents that bulk delete is “intended to be implemented by scanning the whole index”&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/sql-vacuum.html" target="_blank" rel="noopener noreferrer">VACUUM SQL Command&lt;/a>. &lt;code>INDEX_CLEANUP&lt;/code> parameter (&lt;code>AUTO&lt;/code>/&lt;code>ON&lt;/code>/&lt;code>OFF&lt;/code>) and &lt;code>PARALLEL&lt;/code> option for index vacuum&lt;/li>
&lt;li>&lt;a href="https://www.postgresql.org/docs/18/btree.html#BTREE-IMPLEMENTATION" target="_blank" rel="noopener noreferrer">B-Tree Implementation&lt;/a>. B-tree structure, deduplication, and bottom-up deletion (page-deletion and recycling mechanics are detailed in the &lt;code>nbtree/README&lt;/code> listed under Source Code below)&lt;/li>
&lt;/ul>
&lt;h3 id="postgresql-source-code">PostgreSQL Source Code&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/nbtree/nbtree.c" target="_blank" rel="noopener noreferrer">&lt;code>src/backend/access/nbtree/nbtree.c&lt;/code>&lt;/a>. B-tree vacuum implementation: &lt;code>btbulkdelete()&lt;/code> then &lt;code>btvacuumscan()&lt;/code> (physical-order scan of all index pages except the metapage), &lt;code>btvacuumpage()&lt;/code> (per-page processing)&lt;/li>
&lt;li>&lt;a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/heap/vacuumlazy.c" target="_blank" rel="noopener noreferrer">&lt;code>src/backend/access/heap/vacuumlazy.c&lt;/code>&lt;/a>. Main VACUUM implementation: heap scan, dead tuple collection, &lt;code>lazy_vacuum_all_indexes()&lt;/code> (iterates all indexes calling &lt;code>ambulkdelete&lt;/code>), bypass optimization (&lt;code>BYPASS_THRESHOLD_PAGES = 0.02&lt;/code>)&lt;/li>
&lt;li>&lt;a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/include/access/nbtree.h" target="_blank" rel="noopener noreferrer">&lt;code>src/include/access/nbtree.h&lt;/code>&lt;/a>. B-tree data structures and constants&lt;/li>
&lt;li>&lt;a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/nbtree/README" target="_blank" rel="noopener noreferrer">&lt;code>src/backend/access/nbtree/README&lt;/code>&lt;/a>. Design notes on B-tree page deletion, recycling, and the half-dead/pin-based deletion protocol&lt;/li>
&lt;/ul></content:encoded><author>Pep Pla</author><category>PostgreSQL</category><category>autovacuum</category><category>vacuum</category><category>mvcc</category><category>pg_pep</category><media:thumbnail url="https://percona.community/blog/2026/06/pep_autovacuum_cover_hu_e3757fe87eedc710.jpg"/><media:content url="https://percona.community/blog/2026/06/pep_autovacuum_cover_hu_aa846de768730ab8.jpg" medium="image"/></item><item><title>Building Smart Semantic Search using PostgreSQL and pgvector. Part 3 - Hybrid Search, Percona Blog, and Widget Improvements</title><link>https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/</link><guid>https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/</guid><pubDate>Tue, 30 Jun 2026 11:00:00 UTC</pubDate><description>Here I write about what I changed after the first launch: hybrid search, filters and counts on the search page, the widget layout, indexer hardening, and the Percona Blog in the index. People often type short words or names, and pure vector search is weak at that. When I turned search on about a month ago I asked for feedback in Part 1, and most of what follows came from that.</description><content:encoded>&lt;p>Here I write about what I changed after the first launch: hybrid search, filters and counts on the search page, the widget layout, indexer hardening, and the Percona Blog in the index. People often type short words or names, and pure vector search is weak at that. When I turned search on about a month ago I asked for feedback in Part 1, and most of what follows came from that.&lt;/p>
&lt;p>&lt;a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1&lt;/a> is the introduction and stack overview. &lt;a href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Part 2&lt;/a> is Postgres, chunks, SQL, and the indexer.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2608" height="1676" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/search-part-3-search-page-tabs-pgbackuprest_hu_bb79316f943175f3.webp 768w, https://percona.community/blog/2026/06/search-part-3-search-page-tabs-pgbackuprest_hu_88263e76b5c96871.webp 480w, https://percona.community/blog/2026/06/search-part-3-search-page-tabs-pgbackuprest_hu_85ebba16e2270f0a.webp 1400w" src="https://percona.community/blog/2026/06/search-part-3-search-page-tabs-pgbackuprest_hu_85ebba16e2270f0a.webp" alt="Search page with pgbackrest, filter tabs and match quality slider" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="a-month-in-production">A month in production&lt;/h2>
&lt;p>The API and indexer run in Docker on EC2. The engine is PostgreSQL with &lt;a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector&lt;/a>. On the site there is a search widget in the blog header and a full results page at &lt;a href="https://percona.community/search/" target="_blank" rel="noopener noreferrer">percona.community/search/&lt;/a>. Both call the same API. Search has been up since launch. I log queries for debugging and picked up feedback from colleagues, mostly in conversation and from my own tests, not from an analytics dashboard.&lt;/p>
&lt;p>I keep &lt;code>search_history&lt;/code> in Postgres as an engineering log: timings, regressions after hybrid changes, vector vs keyword splits. To be honest, the first month is mostly my smoke tests. Lots of &lt;code>test&lt;/code>, the same names repeated while I debugged person-search, random widget checks. A “top user queries” chart from that log would look like internal QA, not real audience insight, so I am not publishing one here.&lt;/p>
&lt;p>From feedback and from what I tried by hand, three kinds of queries kept showing up.&lt;/p>
&lt;p>Short ones are a single product token (&lt;code>pgbackrest&lt;/code>, &lt;code>timescaledb&lt;/code>) or a person’s name (contributor or Percona Blog author). Long phrases like “zero downtime database migration” or “replication lag troubleshooting” still work fine with vector-only search, as in parts 1 and 2. People also want filters by content type and honest counts on the tabs, so it does not feel broken when the UI shows 30 cards but the tab says 900 matched.&lt;/p>
&lt;p>That shaped what I worked on in June. The table below is illustrative, not a leaderboard from production logs.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Example query&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Why it matters&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>timescaledb&lt;/code>&lt;/td>
&lt;td>keyword&lt;/td>
&lt;td>one word, weak vector, needs text&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PMM&lt;/code>&lt;/td>
&lt;td>keyword&lt;/td>
&lt;td>short product name&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Peter Zaitsev&lt;/code>&lt;/td>
&lt;td>person&lt;/td>
&lt;td>contributor profile plus author articles&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>slow queries mysql&lt;/code>&lt;/td>
&lt;td>hybrid&lt;/td>
&lt;td>short tech phrase, not pure semantics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>zero downtime database migration&lt;/code>&lt;/td>
&lt;td>semantic&lt;/td>
&lt;td>long query as designed&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>In &lt;code>/demo&lt;/code> under History I left the log and the Word statistics tab for myself. When traffic grows and I filter out test noise, that view will be more useful.&lt;/p>
&lt;h2 id="widget-and-the-search-page">Widget and the search page&lt;/h2>
&lt;p>The widget has two modes, the popup in the header and the full &lt;code>/search/&lt;/code> page (see the screenshot at the top of the post for tabs, counts, and the match quality slider).&lt;/p>
&lt;p>From feedback I added filter tabs with multi-select (All, Blog, Percona Blog, Events, Talks, Contributors). The choice goes into the URL as &lt;code>?type=blog,talk&lt;/code>. Tabs show counts in parentheses. Without a query that is indexed totals from public &lt;code>GET /health&lt;/code>. After a search it is match counts per type from a separate &lt;code>COUNT&lt;/code> in the API, without loading every card. There is a “Minimum match quality” slider for &lt;code>min_score&lt;/code>, saved in &lt;code>localStorage&lt;/code> and the URL. Search statistics (timings, vector vs keyword, score range) sit behind a compact link instead of taking half the page. Cards on &lt;code>/search/&lt;/code> have a preview image, type, score, and a shorter excerpt.&lt;/p>
&lt;p>I set the search page content width to 960px with tabs and controls centered. Small thing, but it reads better on mobile and desktop.&lt;/p>
&lt;p>The next two screenshots use &lt;code>Peter Zaitsev&lt;/code> as the person-search demo. &lt;code>-pz-&lt;/code> in the filenames is shorthand for that query.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1998" height="1286" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/search-part-3-search-stat-tabs-pz_hu_2462faed1694cb82.webp 768w, https://percona.community/blog/2026/06/search-part-3-search-stat-tabs-pz_hu_9a23a907e86d50fd.webp 480w, https://percona.community/blog/2026/06/search-part-3-search-stat-tabs-pz_hu_7d82ba220cb4f425.webp 1400w" src="https://percona.community/blog/2026/06/search-part-3-search-stat-tabs-pz_hu_7d82ba220cb4f425.webp" alt="Search statistics for Peter Zaitsev, timings, hybrid split, tab match counts" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The popup in the site header uses the same API. Metadata stays compact above the results.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2476" height="1728" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/search-part-3-widget-pz-top_hu_d45cc66eb637e2c6.webp 768w, https://percona.community/blog/2026/06/search-part-3-widget-pz-top_hu_25bb67efa15ffca1.webp 480w, https://percona.community/blog/2026/06/search-part-3-widget-pz-top_hu_6a5ae3643c6ef990.webp 1400w" src="https://percona.community/blog/2026/06/search-part-3-widget-pz-top_hu_6a5ae3643c6ef990.webp" alt="Header popup, Peter Zaitsev, contributor profile first" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="why-short-queries-hurt-pure-vector-search">Why short queries hurt pure vector search&lt;/h2>
&lt;p>The embedding model is trained on phrases and context. A one-token query like &lt;code>pgbackrest&lt;/code> or two words without a clear topic like &lt;code>Peter Zaitsev&lt;/code> gives a short vector with a weak signal. In 768 dimensions many irrelevant chunks still land “not too far away”, especially across 18k chunks.&lt;/p>
&lt;p>With &lt;code>Peter Zaitsev&lt;/code>, vector search pulled random old posts that mention the name in the body. The contributor profile and recent author articles were not on top. With &lt;code>pgbackrest&lt;/code>, semantics blurred and the top hits were “something about backup”, not necessarily pgBackRest.&lt;/p>
&lt;p>A long query like “how to reduce replication lag on PostgreSQL” is a different story. Query and documents are rich in context and cosine similarity behaves predictably. For that I kept vector-only.&lt;/p>
&lt;h2 id="hybrid-search-options">Hybrid search options&lt;/h2>
&lt;p>I needed a stronger keyword leg for short queries without breaking semantic search for long ones.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Option&lt;/th>
&lt;th>Pros&lt;/th>
&lt;th>Cons / why not now&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>PostgreSQL FTS (&lt;code>to_tsvector&lt;/code>, &lt;code>ts_rank&lt;/code>)&lt;/td>
&lt;td>built-in, GIN indexes&lt;/td>
&lt;td>dictionaries, stemming for names and brands&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pg_trgm&lt;/code>&lt;/td>
&lt;td>good for typos&lt;/td>
&lt;td>heavier at scale, extra indexes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OpenSearch / Elasticsearch&lt;/td>
&lt;td>mature BM25&lt;/td>
&lt;td>another cluster, I skipped this in Part 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ILIKE plus heuristic score&lt;/td>
&lt;td>fast to ship, one Postgres, easy to debug&lt;/td>
&lt;td>not full BM25, &lt;code>%pattern%&lt;/code> without GIN slows down at huge scale&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I started with ILIKE as step one. Something to compare against, with a clear upgrade path to FTS, trigram, or RRF. At community scale, about 7k documents, it is acceptable for now.&lt;/p>
&lt;h2 id="how-hybrid-search-works-in-the-api">How hybrid search works in the API&lt;/h2>
&lt;h3 id="query-mode">Query mode&lt;/h3>
&lt;p>&lt;code>detect_search_mode()&lt;/code> in &lt;code>search_hybrid.py&lt;/code> picks one of three modes.&lt;/p>
&lt;ul>
&lt;li>&lt;code>keyword&lt;/code> for one token (&lt;code>timescaledb&lt;/code>, &lt;code>audit_log&lt;/code>)&lt;/li>
&lt;li>&lt;code>person&lt;/code> for 2-4 name-like tokens (&lt;code>Peter Zaitsev&lt;/code>), with stop words like &lt;code>percona&lt;/code>, &lt;code>mysql&lt;/code>&lt;/li>
&lt;li>&lt;code>semantic&lt;/code> for everything else, vector only&lt;/li>
&lt;/ul>
&lt;h3 id="two-search-legs">Two search legs&lt;/h3>
&lt;p>For &lt;code>keyword&lt;/code> and &lt;code>person&lt;/code> I run vector search as before (best chunk per document, &lt;code>score >= min_score&lt;/code>, &lt;code>LIMIT N&lt;/code>) and keyword SQL on &lt;code>pages&lt;/code>, plus a chunk body check when needed:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ILIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'%Peter Zaitsev%'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ESCAPE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'\'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">OR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ILIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'%Peter Zaitsev%'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ESCAPE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'\'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">OR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ILIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'%Peter Zaitsev%'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ESCAPE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'\'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">OR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">…&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tags&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">chunk&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">…&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">CASE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHEN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ILIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">…&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">THEN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">WHEN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ILIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">…&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">THEN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ELSE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">END&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DESC&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NULLS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LAST&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For &lt;code>person&lt;/code> I add ILIKE on each name part in author and title. Heuristic score 0.72-0.99 in Python (&lt;code>score_keyword_row&lt;/code>). Exact author match ranks above a mention in the body.&lt;/p>
&lt;p>ILIKE and user input. The SQL above uses literals for readability. In code every pattern is &lt;code>ILIKE %s ESCAPE '\\'&lt;/code> and psycopg2 binds the value. The query string is never concatenated into SQL, so classic injection like &lt;code>' OR 1=1 --&lt;/code> does not apply. Parameters alone are not enough for ILIKE because &lt;code>%&lt;/code> and &lt;code>_&lt;/code> are wildcards inside the pattern. I escape &lt;code>\&lt;/code>, &lt;code>%&lt;/code>, and &lt;code>_&lt;/code> in Python, wrap the term in &lt;code>%…%&lt;/code>, and set &lt;code>ESCAPE '\\'&lt;/code> in SQL so a user cannot widen the match with their own &lt;code>%&lt;/code>. Chunk body checks use &lt;code>POSITION(LOWER(%s) IN LOWER(chunk_text))&lt;/code> with the same bound parameter.&lt;/p>
&lt;h3 id="merge">Merge&lt;/h3>
&lt;p>&lt;code>merge_search_results()&lt;/code> unions by &lt;code>slug&lt;/code>. If both legs match the same document, I keep the higher score. Then sort by score, then recency for dated content. For &lt;code>person&lt;/code>, the matching contributor profile goes first.&lt;/p>
&lt;p>The user’s &lt;code>min_score&lt;/code> is applied after merge so weak vector-only hits do not slip through when the threshold is raised.&lt;/p>
&lt;h3 id="tab-counts">Tab counts&lt;/h3>
&lt;p>For badges like &lt;code>Percona Blog (905)&lt;/code> the API runs &lt;code>COUNT(DISTINCT slug) … GROUP BY content_type&lt;/code> with the same keyword conditions, no row &lt;code>LIMIT&lt;/code>. The UI still shows 30 best cards. The tab number is how many matched in total.&lt;/p>
&lt;p>The response includes &lt;code>stats.search_mode&lt;/code> (&lt;code>semantic&lt;/code>, &lt;code>keyword&lt;/code>, or &lt;code>person&lt;/code>) and timings split into &lt;code>vector_db_ms&lt;/code> and &lt;code>keyword_db_ms&lt;/code>.&lt;/p>
&lt;pre class="mermaid">
flowchart LR
Q["Query"] --> D{detect_search_mode}
D -->|semantic| V["vector_search"]
D -->|keyword / person| V
D -->|keyword / person| K["keyword_search ILIKE"]
V --> M["merge by slug"]
K --> M
M --> F["filter min_score"]
F --> R["≤ limit cards"]
K --> C["COUNT for tabs"]
&lt;/pre>
&lt;h2 id="the-bug-that-broke-person-search-in-production">The bug that broke person search in production&lt;/h2>
&lt;p>After I shipped hybrid search, queries like &lt;code>Peter Zaitsev&lt;/code> returned 500. The widget showed “Oops, sorry - something went wrong on our end.” Semantic queries still worked.&lt;/p>
&lt;p>API logs:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">psycopg2.errors.InternalError_: could not load library "/usr/pgsql-18/lib/llvmjit.so":
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">undefined symbol: _ZSt21__glibcxx_assert_failPKciS0_S0_&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Hybrid person search builds a heavy plan with &lt;code>ILIKE&lt;/code> and &lt;code>POSITION(LOWER(...) IN chunk_text)&lt;/code> over 18k chunks. PostgreSQL tried to JIT-compile it and failed loading &lt;code>llvmjit.so&lt;/code>. The same error is described on the &lt;a href="https://forums.percona.com/t/llvmjit-so-fails-to-load-in-percona-postgresql-17-container/40690" target="_blank" rel="noopener noreferrer">Percona forum&lt;/a>. I set &lt;code>jit = off&lt;/code> on every API connection and search worked again.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">off&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- on every API connection&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For tab &lt;code>COUNT&lt;/code> in person mode I count on &lt;code>pages&lt;/code> without scanning chunks. Author and title cover name search. Full keyword search with body still runs with &lt;code>jit = off&lt;/code>.&lt;/p>
&lt;p>On PG 18 with pgvector and text subqueries, test hybrid on person and keyword queries, not only long phrases. The planner behaves differently.&lt;/p>
&lt;h2 id="percona-blog-in-one-index">Percona Blog in one index&lt;/h2>
&lt;p>The community site already had blog posts, events, talks, and contributors. Without the &lt;a href="https://www.percona.com/blog/" target="_blank" rel="noopener noreferrer">official Percona Blog&lt;/a> search felt incomplete. Talks and community posts link there every day.&lt;/p>
&lt;h3 id="what-i-had-to-build">What I had to build&lt;/h3>
&lt;ol>
&lt;li>New &lt;code>content_type&lt;/code>: &lt;code>percona_blog&lt;/code>, its own RSS feed at &lt;code>https://www.percona.com/blog/feed/&lt;/code>, separate crawler rules.&lt;/li>
&lt;li>Same pipeline as the rest: RSS, HTML, chunks, embedding, &lt;code>pages&lt;/code> and &lt;code>community_nomic&lt;/code>. Same model &lt;code>nomic-embed-text-v1&lt;/code>, prefixes &lt;code>search_document:&lt;/code> and &lt;code>search_query:&lt;/code> unchanged.&lt;/li>
&lt;li>WordPress and RSS quirks on Percona Blog. Images in Open Graph, author in metadata, full text only on the HTML page. I added &lt;code>fetch_percona_blog_image()&lt;/code> and a &lt;code>percona_blog&lt;/code> branch in &lt;code>crawler.py&lt;/code>.&lt;/li>
&lt;li>Scale. Roughly 6200+ new documents. The index went from about 800 to about 7000 pages and 18,000 chunks. HNSW on a single Postgres on EC2 still copes, but a full re-index takes hours, not minutes.&lt;/li>
&lt;/ol>
&lt;h3 id="indexer-after-crashes">Indexer after crashes&lt;/h3>
&lt;p>The first full Percona Blog crawl failed several times. Worker OOM, Docker restarts, a long RSS walk. I hardened the indexer:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Problem&lt;/th>
&lt;th>Fix&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Re-crawl from scratch after a crash&lt;/td>
&lt;td>&lt;code>skip_known&lt;/code>, skip URLs already in &lt;code>pages&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Resume mid-RSS&lt;/td>
&lt;td>start feed page near &lt;code>indexed_count // 10&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cancel did not work&lt;/td>
&lt;td>&lt;code>cancel_requested&lt;/code> checks between RSS pages, before fetch and encode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OOM during embedding&lt;/td>
&lt;td>&lt;code>EMBED_BATCH_SIZE=8&lt;/code>, batched &lt;code>model.encode()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>No visibility&lt;/td>
&lt;td>&lt;code>progress_log&lt;/code> on &lt;code>indexer_runs&lt;/code>, live log in demo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stuck task after kill&lt;/td>
&lt;td>on worker startup honour cancel, re-queue resume tasks&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Same worker and &lt;code>index_queue&lt;/code>, but behavior closer to production ETL than a one-off script.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2178" height="1338" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/search-part-3-sources_hu_51034da64e9d7230.webp 768w, https://percona.community/blog/2026/06/search-part-3-sources_hu_1c526d2618d64e45.webp 480w, https://percona.community/blog/2026/06/search-part-3-sources_hu_a173227c173ddf94.webp 1400w" src="https://percona.community/blog/2026/06/search-part-3-sources_hu_a173227c173ddf94.webp" alt="Admin dashboard, documents, chunks, and percona_blog in the index" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2300" height="1786" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/search-part-3-index_hu_c7982673de95288b.webp 768w, https://percona.community/blog/2026/06/search-part-3-index_hu_e7e6c54a98888924.webp 480w, https://percona.community/blog/2026/06/search-part-3-index_hu_4467be6bdead58b3.webp 1400w" src="https://percona.community/blog/2026/06/search-part-3-index_hu_4467be6bdead58b3.webp" alt="Percona Blog indexing, progress log and current URL" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="whats-next">What’s next&lt;/h2>
&lt;ul>
&lt;li>PostgreSQL FTS instead of bare ILIKE for the keyword leg&lt;/li>
&lt;li>Pagination on &lt;code>/search/&lt;/code> if I need to go past 30. Nobody needs all 900 author posts on one screen, “load 30 more” is enough&lt;/li>
&lt;li>More sources from the Part 1 roadmap: video, GitHub, forum&lt;/li>
&lt;/ul>
&lt;h2 id="try-it-yourself">Try it yourself&lt;/h2>
&lt;p>Popup search is in the &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a> header. Full results are at &lt;a href="https://percona.community/search/?q=zero+downtime+database+migration" target="_blank" rel="noopener noreferrer">percona.community/search/&lt;/a>.&lt;/p>
&lt;p>Examples to try. These are demos of different modes, not a top from the log.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Query&lt;/th>
&lt;th>Mode&lt;/th>
&lt;th>What to check&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>zero downtime database migration&lt;/code>&lt;/td>
&lt;td>semantic&lt;/td>
&lt;td>long phrase, vector-only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>replication lag troubleshooting&lt;/code>&lt;/td>
&lt;td>semantic&lt;/td>
&lt;td>same&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pgbackrest&lt;/code>&lt;/td>
&lt;td>keyword&lt;/td>
&lt;td>one-word product, hybrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Peter Zaitsev&lt;/code>&lt;/td>
&lt;td>person&lt;/td>
&lt;td>contributor plus author articles&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>best pizza recipe napoli&lt;/code>&lt;/td>
&lt;td>semantic&lt;/td>
&lt;td>off-topic, empty above &lt;code>min_score&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>As in &lt;a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1&lt;/a>, I am not publishing the search service code. It is built for percona.community. These posts share observations and ideas you can adapt. Vector search schema and SQL are in &lt;a href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Part 2&lt;/a>. The database is open-source &lt;a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a>.&lt;/p>
&lt;p>If you are building something similar or hit an edge case, leave a comment. In Part 1 I asked for feedback and it led to this post.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>pgvector</category><category>search</category><category>embeddings</category><category>hybrid-search</category><category>docker</category><media:thumbnail url="https://percona.community/blog/2026/06/search-part-3-cover_hu_81dcac00e3048abb.jpg"/><media:content url="https://percona.community/blog/2026/06/search-part-3-cover_hu_317e9b64f850461a.jpg" medium="image"/></item><item><title>Why PostgreSQL needs an AI usage policy</title><link>https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/</link><guid>https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/</guid><pubDate>Fri, 26 Jun 2026 10:00:00 UTC</pubDate><description>We often hear that open source is about people.</description><content:encoded>&lt;p>We often hear that open source is about people.&lt;/p>
&lt;p>People who contribute their time and, in a way, parts of their lives to work on software that is available for everyone without limitations and without licensing costs.&lt;/p>
&lt;p>The more popular a project becomes, the more often we also hear about the need for sustainable open source. Nothing surprising here. Often projects start off as “scratching ones itch” and it’s very appreciated when others notice the work done. The more time passes and the more the work becomes appreciated, the higher the chances that there will be a need to spend more time on the project.&lt;/p>
&lt;p>When projects graduate from a hobby project to software used by thousands of users, or even a foundational building block in production, things get interesting.&lt;/p>
&lt;p>At that point, we may hope to see new contributors joining the project. This would normally be a good thing. But is it still the same in the AI hype era, where anyone can generate almost any content and claim it as their own?&lt;/p>
&lt;p>AI was supposed to be a killer of open source. After all, a lot of publicly available code from open source communities was part of what AI systems trained on. The fear was that as it would become so easy to create our own software, there would not be as much need for the existing open source projects. While this was the hype speaking, we can notice another trend. It became much easier to propose patches, detect and report security threats, or submit code reviews. Even without any developer experience or coding capabilities.&lt;/p>
&lt;h3 id="how-sustainable-is-that-for-the-human-maintainers">How sustainable is that for the human maintainers?&lt;/h3>
&lt;p>It is easy to imagine that there is a very fine line between positively helpful and overwhelming. As with anything unwanted, AI-generated code or text can be harmful to many open source projects. Especially those with a single maintainer treating their project as a spare time hobby and suddenly experiencing a waterfall of &lt;a href="https://en.wikipedia.org/wiki/AI_slop" target="_blank" rel="noopener noreferrer">AI-slop&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1182" height="406" sizes="(max-width: 1182px) 100vw, 1182px" srcset="https://percona.community/blog/2026/06/Jan-ps3_hu_8288ebe9cfda61e2.webp 768w, https://percona.community/blog/2026/06/Jan-ps3_hu_93c3efde79bb1080.webp 480w, https://percona.community/blog/2026/06/Jan-ps3_hu_a13bc5f2ce669b18.webp 1182w" src="https://percona.community/blog/2026/06/Jan-ps3_hu_a13bc5f2ce669b18.webp" alt="RPCS3 plead to vibe coders social media post" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://github.com/RPCS3/rpcs3" target="_blank" rel="noopener noreferrer">Playstation 3 emulator project&lt;/a> recently &lt;a href="https://x.com/rpcs3/status/2053248922974605431?lang=en" target="_blank" rel="noopener noreferrer">RPCS3 posted a plea&lt;/a> to the vibe coders to stop the AI-generated abuse already and they are not alone in this problem.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1450" height="812" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_b7878269a29f2165.webp 768w, https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_87c0f4939170ec8d.webp 480w, https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_a11b544aad7ddffb.webp 1400w" src="https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_a11b544aad7ddffb.webp" alt="FOSDEM 2026 Daniel Stenberg presentation" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Daniel Stenberg from the &lt;a href="https://curl.se/" target="_blank" rel="noopener noreferrer">curl project&lt;/a> captured this well in &lt;a href="https://fosdem.org/2026/schedule/event/B7YKQ7-oss-in-spite-of-ai/" target="_blank" rel="noopener noreferrer">his FOSDEM 2026 talk&lt;/a> summarizing that: “AI gives us the worst and the best, simultaneously.”&lt;/p>
&lt;p>In the same talk, he discussed how curl had to stop its bug bounty program. Curl has also posted on the &lt;a href="https://curl.se/dev/contribute.html#on-ai-use-in-curl" target="_blank" rel="noopener noreferrer">rules of AI use&lt;/a>. Even that was not enough, which led to the “&lt;a href="https://daniel.haxx.se/blog/2026/06/15/curl-summer-of-bliss/" target="_blank" rel="noopener noreferrer">curl summer of bliss&lt;/a>”, where they will:&lt;/p>
&lt;blockquote>
&lt;p>not accept or otherwise handle any vulnerability reports during the month of July 2026.&lt;/p>&lt;/blockquote>
&lt;p>Security reports are an especially sensitive case. An AI-generated vulnerability report is not harmless. Someone has to read it, reproduce it, evaluate it and decide whether it is real. Even when the issue is not there, the work is still very real. Like it or not but when it’s unfounded work that proves a ai-generated false it is abusive.&lt;/p>
&lt;p>Knowing that some projects adopt AI-focused policies, I searched for examples of such policies, using AI obviously 🙂, and stumbled upon &lt;a href="https://github.com/melissawm/open-source-ai-contribution-policies" target="_blank" rel="noopener noreferrer">a very useful (open source!) list that already gathers this kind of information&lt;/a>.&lt;/p>
&lt;p>Further analysis of the resources linked in the list, as of June 2026, shows that most policies allow assisted use, but not “AI as the contributor.”&lt;/p>
&lt;p>Commonly allowed uses include:&lt;/p>
&lt;ul>
&lt;li>drafting code,&lt;/li>
&lt;li>generating tests,&lt;/li>
&lt;li>improving docs,&lt;/li>
&lt;li>debugging,&lt;/li>
&lt;li>summarizing, or asking an LLM for help.&lt;/li>
&lt;/ul>
&lt;p>All of that is usually acceptable as long as the human reviews and owns the result.&lt;/p>
&lt;p>Typically banned practices include:&lt;/p>
&lt;ul>
&lt;li>fully AI-generated PRs with little human engagement&lt;/li>
&lt;li>AI-generated “good first issue” work&lt;/li>
&lt;li>AI as co-author&lt;/li>
&lt;li>automated AI code reviews&lt;/li>
&lt;li>unreviewed agentic output&lt;/li>
&lt;/ul>
&lt;p>It is completely understandable that experienced developers and communities say “no” to submissions of low quality. That would not be sustainable. Maintainers already carry a lot of invisible work, and AI can easily multiply that work if contributors treat it as a shortcut instead of a tool.&lt;/p>
&lt;p>What is very positive for the future of AI-enhanced work is that the general direction seems to be acceptance, as long as there is a human-in-the-loop.&lt;/p>
&lt;p>AI-enhanced work, as long as a human was involved, is possible across a range of open source products: &lt;a href="https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache Airflow&lt;/a>, &lt;a href="https://datafusion.apache.org/contributor-guide/index.html#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache DataFusion&lt;/a>, &lt;a href="https://arrow.apache.org/docs/dev/developers/overview.html#ai-generated-code" target="_blank" rel="noopener noreferrer">Arrow&lt;/a>, &lt;a href="https://github.com/cloudnative-pg/governance/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">CloudNativePG (CNPG)&lt;/a>, &lt;a href="https://devguide.python.org/getting-started/ai-tools/index.html" target="_blank" rel="noopener noreferrer">CPython&lt;/a>, &lt;a href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/submitting-patches/#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Django&lt;/a>, &lt;a href="https://firefox-source-docs.mozilla.org/contributing/ai-coding.html" target="_blank" rel="noopener noreferrer">Firefox&lt;/a>, &lt;a href="https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines" target="_blank" rel="noopener noreferrer">Flutter&lt;/a>, &lt;a href="https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">Ghostty&lt;/a>, &lt;a href="https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy" target="_blank" rel="noopener noreferrer">Gitea&lt;/a>, &lt;a href="https://github.com/Homebrew/brew/blob/main/CONTRIBUTING.md#artificial-intelligencelarge-language-model-aillm-usage" target="_blank" rel="noopener noreferrer">Homebrew&lt;/a>, &lt;a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a>, &lt;a href="https://kernel.org/doc/html/next/process/coding-assistants.html" target="_blank" rel="noopener noreferrer">Linux Kernel&lt;/a>, &lt;a href="https://llvm.org/docs//AIToolPolicy.html" target="_blank" rel="noopener noreferrer">LLVM&lt;/a>, &lt;a href="https://matplotlib.org/devdocs/devel/contribute.html#generative-ai" target="_blank" rel="noopener noreferrer">Matplotlib&lt;/a>, &lt;a href="https://numpy.org/devdocs/dev/ai_policy.html" target="_blank" rel="noopener noreferrer">NumPy&lt;/a>, &lt;a href="https://pandas.pydata.org/docs/dev/development/contributing.html#automated-contributions-policy" target="_blank" rel="noopener noreferrer">Pandas&lt;/a>, &lt;a href="https://github.com/pytorch/pytorch/blob/main/CONTRIBUTING.md#ai-assisted-development" target="_blank" rel="noopener noreferrer">PyTorch&lt;/a>, &lt;a href="https://scipy.github.io/devdocs/dev/conduct/ai_policy.html" target="_blank" rel="noopener noreferrer">SciPy&lt;/a>, &lt;a href="https://docs.sympy.org/dev/contributing/ai-generated-code-policy.html" target="_blank" rel="noopener noreferrer">SymPy&lt;/a>, &lt;a href="https://docs.wagtail.org/en/latest/contributing/general_guidelines.html#general-coding-guidelines" target="_blank" rel="noopener noreferrer">Wagtail&lt;/a>, &lt;a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">Zulip&lt;/a>, and &lt;a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">others&lt;/a>.&lt;/p>
&lt;p>What is interesting is that, at this moment, PostgreSQL does not have any official policy of this sort available.&lt;/p>
&lt;h3 id="slonik-says-i-havent-noticed">Slonik says “I haven’t noticed…”&lt;/h3>
&lt;p>While this may be a problem that does not directly touch PostgreSQL as a database server, it already has an impact on the PostgreSQL ecosystem, which consists of many other extensions and tools.&lt;/p>
&lt;p>The reason may be quite trivial. Even with AI, the entry threshold for PostgreSQL core hacking is still higher than for many other tools. Hackers communicate through mailing lists, and even with the adoption of modern tools like Hackorum.dev, it is still not that easy to work with PostgreSQL compared with many other, more tempting projects.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1672" height="941" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/Jan-waterfall_hu_35e103db02b82d35.webp 768w, https://percona.community/blog/2026/06/Jan-waterfall_hu_c2c517a2982d00ba.webp 480w, https://percona.community/blog/2026/06/Jan-waterfall_hu_23bbbee1b89ee25b.webp 1400w" src="https://percona.community/blog/2026/06/Jan-waterfall_hu_23bbbee1b89ee25b.webp" alt="Beware of elephants drowning in AI slop" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The issue, as I often see it for PostgreSQL, is that there is not much leadership for the wider ecosystem from the core project. Availability of responsible AI usage policies for the ecosystem could make maintainers’ lives easier. And let’s be honest, for many smaller projects, creating such policies from scratch is a burden they could be spared.&lt;/p>
&lt;p>Seems like any help would be appreciated.&lt;/p>
&lt;h3 id="what-now-is-this-over-was-this-a-rant">What now? Is this over? Was this a rant?&lt;/h3>
&lt;p>I like to say, and repeat myself, that “AI usage in open source is all about respect.” To me, this is enough to say all that is needed. People need to communicate. This was meant as a start of the discussion.&lt;/p>
&lt;p>&lt;a href="https://2026.pgconf.eu/" target="_blank" rel="noopener noreferrer">PGConf.EU&lt;/a> is coming in October, as well as many smaller meetups this year. There will be lots of space for hallway track discussions and hopefully some outcomes. Not to mention async communication channels. What I hope is that we can leverage all these channels to propose some solutions, experiment, and get better.&lt;/p>
&lt;p>Let this be a call to action to help us all be more reasonable and more respectful of other people’s time.&lt;/p>
&lt;p>With this in mind,&lt;/p>
&lt;details>
&lt;summary>check out my original text before I refined it with AI if you want to see how it changed.&lt;/summary>
&lt;p>Often we hear how open source is the people. The people who contribute their time, in a way their lives, to produce software available for everyone without limitations. Without licensing cost on the users.&lt;/p>
&lt;p>The more a project becomes popular the higher chances we also hear about the need for sustainable open source from it. Nothing surprising here. At first we want others to notice the work we’ve done. The more times pass and the work becomes appreciated, the higher chances that there will be a need to spend more time on the project.&lt;/p>
&lt;p>When it graduates from a hobby project to software used by thousands of users or even a production founding block things become really interesting. Now we may hope to see new contributors joining the project. This would normally be a good thing but is it the same in the AI hype era where anyone can generate any content and claim it their own?&lt;/p>
&lt;p>AI was supposed to be open source killer because it will become so easy to create our own software and not use open source. While this was the hype speaking, we notice another trend. It became way easier to propose patches, detect and report security threats or submit code reviews. Even without any developer experience or any coding capabilities.&lt;/p>
&lt;h4 id="how-sustainable-for-the-human-maintainers-is-that">How sustainable for the human maintainers is that?&lt;/h4>
&lt;p>It’s easy to imagine that there is a very fine line between positively helpful and overwhelming. As with anything unwanted, the AI generated unwanted code or texts can be harmful to the many ope source projects. Especially those with a single maintainer treating their project as a spare time hobby and experiencing a waterfall of &lt;a href="https://en.wikipedia.org/wiki/AI_slop" target="_blank" rel="noopener noreferrer">AI-slop&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1182" height="406" sizes="(max-width: 1182px) 100vw, 1182px" srcset="https://percona.community/blog/2026/06/Jan-ps3_hu_8288ebe9cfda61e2.webp 768w, https://percona.community/blog/2026/06/Jan-ps3_hu_93c3efde79bb1080.webp 480w, https://percona.community/blog/2026/06/Jan-ps3_hu_a13bc5f2ce669b18.webp 1182w" src="https://percona.community/blog/2026/06/Jan-ps3_hu_a13bc5f2ce669b18.webp" alt="RPCS3 plead to vibe coders social media post" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Playstation 3 emulator RPCS3 posted a plead to the vibe coders to stop the AI-generated abuse already and they are not alone in this problem.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1450" height="812" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_b7878269a29f2165.webp 768w, https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_87c0f4939170ec8d.webp 480w, https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_a11b544aad7ddffb.webp 1400w" src="https://percona.community/blog/2026/06/Jan-fosdem-curl_hu_a11b544aad7ddffb.webp" alt="FOSDEM 2026 Daniel Stenberg presentation" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As Daniel Stenberg from curl says (check out his talk during FOSDEM 2026) “AI gives us the worst and the best - simultaneously”&lt;/p>
&lt;p>Seeing that &lt;a href="https://curl.se/" target="_blank" rel="noopener noreferrer">curl&lt;/a> had to stop their bug bounty program (as discussed in the talk above) and even this was not enough and ended up in the “&lt;a href="https://daniel.haxx.se/blog/2026/06/15/curl-summer-of-bliss/" target="_blank" rel="noopener noreferrer">curl summer of bliss&lt;/a>” where they will:&lt;/p>
&lt;blockquote>
&lt;p>not accept or otherwise handle any vulnerability reports during the month of July 2026.&lt;/p>&lt;/blockquote>
&lt;p>Security reports are an especially sensitive case. A wrong AI-generated vulnerability report is not harmless. Someone has to read it, reproduce it, evaluate it and decide whether it is real. Even when the issue is not there, the work is still very real. Like it or not but when it’s unfounded work that proves a ai-generated false it is abusive.&lt;/p>
&lt;p>Knowing that some projects adopt policies, I searched (using AI obviously 🙂) for examples of such policies and stumbled upon &lt;a href="https://github.com/melissawm/open-source-ai-contribution-policies" target="_blank" rel="noopener noreferrer">a very useful list that gathers such information already&lt;/a>.&lt;/p>
&lt;p>Further analysis of the resources linked in the list (state in June 2026) shows that:&lt;/p>
&lt;ul>
&lt;li>Most policies allow assisted use, not “AI as the contributor.”&lt;/li>
&lt;li>Commonly allowed uses include drafting code, generating tests, improving docs, debugging, summarizing, or asking an LLM for help, as long as the human reviews and owns the result.&lt;/li>
&lt;li>Typically banned practices include:
&lt;ul>
&lt;li>Fully AI-generated PRs with little human engagement&lt;/li>
&lt;li>AI-generated “good first issue” work&lt;/li>
&lt;li>AI as co-author&lt;/li>
&lt;li>Automated AI code reviews&lt;/li>
&lt;li>Unreviewed agentic output&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>It’s only understandable that experienced developers and Communities say “no” to low quality submissions. That would not be sustainable. What is very positive for the future of AI enhanced work is that the repo shows general acceptance as long as there is a “human in the loop”. AI enhanced work as long as human was involved is possible across a range of open source products: &lt;a href="https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache Airflow&lt;/a>, &lt;a href="https://datafusion.apache.org/contributor-guide/index.html#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache DataFusion&lt;/a>, &lt;a href="https://arrow.apache.org/docs/dev/developers/overview.html#ai-generated-code" target="_blank" rel="noopener noreferrer">Arrow&lt;/a>, &lt;a href="https://github.com/cloudnative-pg/governance/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">CloudNativePG (CNPG)&lt;/a>, &lt;a href="https://devguide.python.org/getting-started/ai-tools/index.html" target="_blank" rel="noopener noreferrer">CPython&lt;/a>, &lt;a href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/submitting-patches/#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Django&lt;/a>, &lt;a href="https://firefox-source-docs.mozilla.org/contributing/ai-coding.html" target="_blank" rel="noopener noreferrer">Firefox&lt;/a>, &lt;a href="https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines" target="_blank" rel="noopener noreferrer">Flutter&lt;/a>, &lt;a href="https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">Ghostty&lt;/a>, &lt;a href="https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy" target="_blank" rel="noopener noreferrer">Gitea&lt;/a>, &lt;a href="https://github.com/Homebrew/brew/blob/main/CONTRIBUTING.md#artificial-intelligencelarge-language-model-aillm-usage" target="_blank" rel="noopener noreferrer">Homebrew&lt;/a>, &lt;a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a>, &lt;a href="https://kernel.org/doc/html/next/process/coding-assistants.html" target="_blank" rel="noopener noreferrer">Linux Kernel&lt;/a>, &lt;a href="https://llvm.org/docs//AIToolPolicy.html" target="_blank" rel="noopener noreferrer">LLVM&lt;/a>, &lt;a href="https://matplotlib.org/devdocs/devel/contribute.html#generative-ai" target="_blank" rel="noopener noreferrer">Matplotlib&lt;/a>, &lt;a href="https://numpy.org/devdocs/dev/ai_policy.html" target="_blank" rel="noopener noreferrer">NumPy&lt;/a>, &lt;a href="https://pandas.pydata.org/docs/dev/development/contributing.html#automated-contributions-policy" target="_blank" rel="noopener noreferrer">Pandas&lt;/a>, &lt;a href="https://github.com/pytorch/pytorch/blob/main/CONTRIBUTING.md#ai-assisted-development" target="_blank" rel="noopener noreferrer">PyTorch&lt;/a>, &lt;a href="https://scipy.github.io/devdocs/dev/conduct/ai_policy.html" target="_blank" rel="noopener noreferrer">SciPy&lt;/a>, &lt;a href="https://docs.sympy.org/dev/contributing/ai-generated-code-policy.html" target="_blank" rel="noopener noreferrer">SymPy&lt;/a>, &lt;a href="https://docs.wagtail.org/en/latest/contributing/general_guidelines.html#general-coding-guidelines" target="_blank" rel="noopener noreferrer">Wagtail&lt;/a>, &lt;a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">Zulip&lt;/a>, and &lt;a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">others&lt;/a>.&lt;/p>
&lt;h3 id="slonik-says-i-havent-noticed-1">Slonik says “I haven’t noticed…”&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="1672" height="941" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/06/Jan-waterfall_hu_35e103db02b82d35.webp 768w, https://percona.community/blog/2026/06/Jan-waterfall_hu_c2c517a2982d00ba.webp 480w, https://percona.community/blog/2026/06/Jan-waterfall_hu_23bbbee1b89ee25b.webp 1400w" src="https://percona.community/blog/2026/06/Jan-waterfall_hu_23bbbee1b89ee25b.webp" alt="Beware of elephants drowning in AI slop" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>What is interesting that at this moment PostgreSQL does not have any official policy of this sort available. While this may be a problem that does not touch PostgreSQL as a database server it already has an impact on the PostgreSQL ecosystem consisting of many other extensions and tools. The reason may be quite trivial - even with AI the entry threshold for PostgreSQL core hacking is still higher than any other tool. Hackers communicate via mailing lists and even with adoption of modern tools like Hackorum.dev it’s still not that easy to work with PostgreSQL comparing to a lot of other more tempting tools.&lt;/p>
&lt;p>The issue as I often see it for PostgreSQL is that there is not much leadership for the ecosystem from the core. Availability of responsible AI usage policies for the Ecosystem could make the life of maintainers easier and let’s be honest, for a lot of smaller projects that’s a burden they could be spared. Seems like any help would be appreciated.&lt;/p>
&lt;h4 id="what-now-is-this-over-was-this-a-rant-1">What now? Is this over? Was this a rant?&lt;/h4>
&lt;p>I like to say, and repeat myself that “AI usage in open source is all about the respect”. To me this is enough to say all that’s needed. People need to communicate. This was meant as a start of the discussion.&lt;/p>
&lt;p>PGConf.EU is coming in October, as well as many smaller meetups this year. Lots of space for hallway track discussion and hopefully some outcomes. Not to mention async communication channels. What I hope is we can leverage all these channels to propose some solutions, experiment and get better.&lt;/p>
&lt;p>Let it be a call to action to help us all be more reasonable and respectful to others time.&lt;/p>
&lt;p>With this in mind check out my original text I refined with AI if you want to see how it changed. Because of course I polished it to some extent to ensure that the grammar and phrasing is cleaner and crispier 🙂&lt;/p>
&lt;/details>
&lt;p>Because of course I polished it a little to make sure the grammar and phrasing are cleaner and crispier 🙂&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>ai</category><media:thumbnail url="https://percona.community/blog/2026/06/Jan-cover-ai_hu_e2c0da8745ee03cb.jpg"/><media:content url="https://percona.community/blog/2026/06/Jan-cover-ai_hu_5eae7e25afb0e011.jpg" medium="image"/></item><item><title>Guide Multi-Cluster MongoDB on GKE with MCS, Percona Operator</title><link>https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/</link><guid>https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/</guid><pubDate>Fri, 12 Jun 2026 11:00:00 UTC</pubDate><description>Multi-Cluster MongoDB on GKE with MCS Guide Ivan Groenewold, Technical Lead, Percona MongoDB, and I put together a detailed step-by-step guide on deploying the Percona Operator for MongoDB across two GKE clusters using Multi-Cluster Services (MCS)</description><content:encoded>&lt;h1 id="multi-cluster-mongodb-on-gke-with-mcs-guide">Multi-Cluster MongoDB on GKE with MCS Guide&lt;/h1>
&lt;p>&lt;a href="https://www.linkedin.com/in/igroene/" target="_blank" rel="noopener noreferrer">Ivan Groenewold&lt;/a>, Technical Lead, Percona MongoDB, and I put together a detailed step-by-step guide on deploying the Percona Operator for MongoDB across two GKE clusters using Multi-Cluster Services (MCS)&lt;/p>
&lt;p>This guide walks through deploying a highly available MongoDB replica set that spans two GKE clusters using the &lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a> and &lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/multi-cluster-services" target="_blank" rel="noopener noreferrer">GKE Multi-Cluster Services (MCS)&lt;/a>.&lt;/p>
&lt;h2 id="architecture-overview">Architecture Overview&lt;/h2>
&lt;p>Both clusters belong to the same &lt;strong>GKE Fleet&lt;/strong>. MCS gives each cluster DNS names for the
other cluster’s services (&lt;code>*.psmdb.svc.clusterset.local&lt;/code>). &lt;strong>&lt;code>externalNodes&lt;/code>&lt;/strong> in the
Percona CR tells MongoDB to use those names as replica-set members. MCS provides
cross-cluster DNS; &lt;code>externalNodes&lt;/code> wires MongoDB to use it.&lt;/p>
&lt;h3 id="what-runs-on-each-cluster">What runs on each cluster&lt;/h3>
&lt;p>Each site runs a &lt;strong>sharded&lt;/strong> MongoDB cluster (not a single 6-node replset):&lt;/p>
&lt;pre class="mermaid">
flowchart TB
subgraph Main["Main cluster, Operator MANAGED"]
direction TB
MO["mongos ×3"]
MC["cfg replset: cfg-0, cfg-1, cfg-2"]
MR["shard rs0: rs0-0, rs0-1, rs0-2"]
MO --> MC
MO --> MR
end
subgraph Replica["Replica cluster, Operator UNMANAGED"]
direction TB
RO["mongos ×3"]
RC["cfg replset: cfg-0, cfg-1, cfg-2"]
RR["shard rs0: rs0-0, rs0-1, rs0-2"]
RO --> RC
RO --> RR
end
MC &lt;-->|"6 members, config servers"| RC
MR &lt;-->|"6 members, shard data"| RR
&lt;/pre>
&lt;p>Once interconnected, each replset has &lt;strong>6 members&lt;/strong> (3 on main + 3 on replica). One
PRIMARY per replset; the rest are SECONDARY.&lt;/p>
&lt;h3 id="mcs-is-bidirectional">MCS is bidirectional&lt;/h3>
&lt;p>Both clusters &lt;strong>export&lt;/strong> their own services and &lt;strong>import&lt;/strong> the other cluster’s services:&lt;/p>
&lt;pre class="mermaid">
flowchart LR
subgraph Main["Main cluster"]
ExpM["ServiceExport\n(main services)"]
ImpM["ServiceImport\n(replica services)"]
end
subgraph Replica["Replica cluster"]
ExpR["ServiceExport\n(replica services)"]
ImpR["ServiceImport\n(main services)"]
end
ExpM -->|"MCS Fleet"| ImpR
ExpR -->|"MCS Fleet"| ImpM
ImpM --> DNS["*.psmdb.svc.clusterset.local"]
ImpR --> DNS
&lt;/pre>
&lt;p>Each cluster sees &lt;strong>18 ServiceImports&lt;/strong>, 9 from main + 9 from replica.&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>&lt;code>gcloud&lt;/code> CLI installed and authenticated&lt;/li>
&lt;li>&lt;code>kubectl&lt;/code> installed&lt;/li>
&lt;li>&lt;code>yq&lt;/code> installed (&lt;code>brew install yq&lt;/code> on macOS or &lt;code>apt install yq&lt;/code> on Linux)&lt;/li>
&lt;li>A GCP project with billing enabled&lt;/li>
&lt;li>Owner or Editor role on the project&lt;/li>
&lt;/ul>
&lt;p>If you want to see all the command in a Readmefile, see the Github repository &lt;a href="https://github.com/edithturn/psmdb-operator-multicluster-demo/blob/main/README.md" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h2 id="file-layout">File Layout&lt;/h2>
&lt;p>After completing this guide you will have:&lt;/p>
&lt;p>&lt;strong>Kubeconfigs&lt;/strong> (in &lt;code>~/.kube/psmdb-demo/&lt;/code>, outside this repo):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">~/.kube/psmdb-demo/gcp-main_config # kubeconfig for main cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~/.kube/psmdb-demo/gcp-replica_config # kubeconfig for replica cluster&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Manifests and exports&lt;/strong> (in this working directory):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cr-main.yaml # Main cluster initial config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cr-main-after.yaml # Main cluster config with externalNodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cr-replica.yaml # Replica cluster config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cr-replica-after.yaml # Replica cluster config with externalNodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The following files are local only, created during the guide, listed in &lt;code>.gitignore&lt;/code>, &lt;strong>do not commit&lt;/strong> (contain passwords, TLS keys, and encryption keys):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">my-cluster-secrets.yml # exported from main (do not apply directly)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-ssl.yml # exported from main (do not apply directly)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-ssl-internal.yml # exported from main (do not apply directly)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-cluster-name-mongodb-encryption-key.yml # exported from main (do not apply directly)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-cluster-secrets-replica.yaml # modified for replica, apply this
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-ssl.yml # modified for replica, apply this
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-ssl-internal.yml # modified for replica, apply this
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-cluster-name-mongodb-encryption-key-replica.yml # modified for replica, apply this&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>Why two versions of cr-main.yaml?&lt;/strong>
The initial &lt;code>cr-main.yaml&lt;/code> deploys the cluster without knowing the replica node addresses.
After the replica cluster is running and ServiceImports are confirmed, &lt;code>cr-main-after.yaml&lt;/code>
adds &lt;code>externalNodes&lt;/code> to interconnect the two clusters. This avoids DNS failures during
initial deployment.&lt;/p>&lt;/blockquote>
&lt;h2 id="step-1-set-your-project-id">Step 1: Set your project ID&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PROJECT_ID&lt;/span>&lt;span class="o">=&lt;/span>your_project_id&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$PROJECT_ID&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-2-enable-required-gcp-apis">Step 2: Enable required GCP APIs&lt;/h2>
&lt;p>These APIs are required for MCS, Fleet, and Workload Identity to work.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud services &lt;span class="nb">enable&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> multiclusterservicediscovery.googleapis.com &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> gkehub.googleapis.com &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> cloudresourcemanager.googleapis.com &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> trafficdirector.googleapis.com &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> dns.googleapis.com &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --project &lt;span class="nv">$PROJECT_ID&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output: each API shows &lt;code>Enabling API...&lt;/code> then &lt;code>Operation finished successfully&lt;/code>.&lt;/p>
&lt;h2 id="step-3-create-two-gke-clusters">Step 3: Create two GKE clusters&lt;/h2>
&lt;p>Both clusters must be created with &lt;code>--workload-metadata=GKE_METADATA&lt;/code> and &lt;code>--workload-pool&lt;/code>
to enable Workload Identity Federation, which is required by the MCS importer.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Main cluster&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container clusters create main-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --zone us-central1-a &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --machine-type n1-standard-4 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --num-nodes&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --workload-metadata&lt;span class="o">=&lt;/span>GKE_METADATA &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --workload-pool&lt;span class="o">=&lt;/span>&lt;span class="nv">$PROJECT_ID&lt;/span>.svc.id.goog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Replica cluster&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container clusters create replica-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --zone us-central1-a &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --machine-type n1-standard-4 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --num-nodes&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --workload-metadata&lt;span class="o">=&lt;/span>GKE_METADATA &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --workload-pool&lt;span class="o">=&lt;/span>&lt;span class="nv">$PROJECT_ID&lt;/span>.svc.id.goog&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Both clusters use &lt;code>us-central1-a&lt;/code> here for simplicity. In a production setup,
use different zones or regions (e.g. &lt;code>us-east1-b&lt;/code>) for the replica to achieve
true regional isolation.&lt;/p>&lt;/blockquote>
&lt;h2 id="step-4-enable-mcs-and-register-clusters-to-the-fleet">Step 4: Enable MCS and register clusters to the Fleet&lt;/h2>
&lt;p>GKE uses a Fleet to group clusters. There is exactly one Fleet per GCP project,
automatically named after the project ID. MCS works across all clusters in the same Fleet.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Enable MCS at the Fleet level&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container fleet multi-cluster-services &lt;span class="nb">enable&lt;/span> --project &lt;span class="nv">$PROJECT_ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Register main cluster to the Fleet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container fleet memberships register main-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --gke-cluster us-central1-a/main-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --enable-workload-identity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Register replica cluster to the Fleet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container fleet memberships register replica-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --gke-cluster us-central1-a/replica-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --enable-workload-identity&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-5-grant-iam-permissions-to-the-mcs-importer">Step 5: Grant IAM permissions to the MCS Importer&lt;/h2>
&lt;p>The MCS Importer is a GKE-managed pod in the &lt;code>gke-mcs&lt;/code> namespace on each cluster.
Its job is to watch for &lt;code>ServiceExport&lt;/code> resources and create &lt;code>ServiceImport&lt;/code> objects
on other clusters. It needs read access to your VPC network configuration to do this.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Get the numeric project number (different from the project ID string)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PROJECT_NUMBER&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>gcloud projects describe &lt;span class="nv">$PROJECT_ID&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --format&lt;span class="o">=&lt;/span>&lt;span class="s2">"value(projectNumber)"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Grant compute.networkViewer to the MCS importer service account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud projects add-iam-policy-binding &lt;span class="nv">$PROJECT_ID&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --member &lt;span class="s2">"principal://iam.googleapis.com/projects/&lt;/span>&lt;span class="nv">$PROJECT_NUMBER&lt;/span>&lt;span class="s2">/locations/global/workloadIdentityPools/&lt;/span>&lt;span class="nv">$PROJECT_ID&lt;/span>&lt;span class="s2">.svc.id.goog/subject/ns/gke-mcs/sa/gke-mcs-importer"&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --role &lt;span class="s2">"roles/compute.networkViewer"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-6-verify-mcs-is-active-on-both-clusters">Step 6: Verify MCS is active on both clusters&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud container fleet multi-cluster-services describe --project &lt;span class="nv">$PROJECT_ID&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output, both clusters must show &lt;code>code: OK&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">membershipStates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">projects/XXXXXXX/locations/us-central1/memberships/main-cluster&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">state&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">code&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">OK&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Firewall successfully updated&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">projects/XXXXXXX/locations/us-central1/memberships/replica-cluster&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">state&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">code&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">OK&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Firewall successfully updated&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">resourceState&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">state&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ACTIVE&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>If you see &lt;code>code: PENDING&lt;/code> wait 2–3 minutes and re-run. If you see errors,
check that both clusters were created with &lt;code>--workload-pool&lt;/code> and the IAM
binding in Step 5 was applied successfully.&lt;/p>&lt;/blockquote>
&lt;h2 id="step-7-generate-kubeconfig-files">Step 7: Generate kubeconfig files&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>Security:&lt;/strong> Kubeconfig files contain credentials that grant access to your clusters.
Keep both files in &lt;code>~/.kube/psmdb-demo&lt;/code> only, do not copy them elsewhere, commit them
to version control, or share them with anyone.&lt;/p>&lt;/blockquote>
&lt;p>Store kubeconfig files in a dedicated directory outside this project:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mkdir -p ~/.kube/psmdb-demo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod &lt;span class="m">700&lt;/span> ~/.kube/psmdb-demo&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Generate kubeconfig for main cluster&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">KUBECONFIG&lt;/span>&lt;span class="o">=&lt;/span>~/.kube/psmdb-demo/gcp-main_config gcloud container clusters &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> get-credentials main-cluster --zone us-central1-a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Generate kubeconfig for replica cluster&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">KUBECONFIG&lt;/span>&lt;span class="o">=&lt;/span>~/.kube/psmdb-demo/gcp-replica_config gcloud container clusters &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> get-credentials replica-cluster --zone us-central1-a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod &lt;span class="m">600&lt;/span> ~/.kube/psmdb-demo/gcp-main_config ~/.kube/psmdb-demo/gcp-replica_config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify both files were created:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ls -la ~/.kube/psmdb-demo/gcp-main_config ~/.kube/psmdb-demo/gcp-replica_config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify each connects to the correct cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl --kubeconfig ~/.kube/psmdb-demo/gcp-main_config get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl --kubeconfig ~/.kube/psmdb-demo/gcp-replica_config get nodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>Two terminals, set up once:&lt;/strong> Open &lt;strong>two terminal windows&lt;/strong> for the rest of this
guide. Run each export &lt;strong>once&lt;/strong> when you open the terminal, you do not need to repeat
it in later steps unless you open a new window:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Terminal&lt;/th>
&lt;th>Cluster&lt;/th>
&lt;th>Run once when opening the terminal&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Terminal 1&lt;/strong>&lt;/td>
&lt;td>Main&lt;/td>
&lt;td>&lt;code>export KUBECONFIG=~/.kube/psmdb-demo/gcp-main_config&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Terminal 2&lt;/strong>&lt;/td>
&lt;td>Replica&lt;/td>
&lt;td>&lt;code>export KUBECONFIG=~/.kube/psmdb-demo/gcp-replica_config&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Verify:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get nodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From Step 8 onward, every &lt;code>kubectl&lt;/code> block is labeled &lt;strong>Terminal 1&lt;/strong> or &lt;strong>Terminal 2&lt;/strong>
only. Run the command in the matching terminal.
Re-export only if you open a &lt;strong>new&lt;/strong> terminal window.&lt;/p>&lt;/blockquote>
&lt;p>Example: This is how the cluster looks like:&lt;/p>
&lt;p>&lt;strong>Terminal 1 · main cluster&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-main-cluster-default-pool-9c0082b4-19wj Ready &lt;none> 68m v1.35.3-gke.2190000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-main-cluster-default-pool-9c0082b4-q78p Ready &lt;none> 68m v1.35.3-gke.2190000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-main-cluster-default-pool-9c0082b4-rb6r Ready &lt;none> 68m v1.35.3-gke.2190000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Terminal 2 · replica cluster&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-1qkb Ready &lt;none> 56m v1.35.3-gke.2190000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-gl5j Ready &lt;none> 56m v1.35.3-gke.2190000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-h6hk Ready &lt;none> 56m v1.35.3-gke.2190000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-8-grant-cluster-admin-permissions-to-your-account">Step 8: Grant cluster-admin permissions to your account&lt;/h2>
&lt;p>GCP project access and Kubernetes permissions inside each cluster are separate,
Step 7’s kubeconfig lets you authenticate, but from Step 9 onward you need
cluster-wide rights to install the operator and deploy MongoDB. Main and replica are
independent clusters with their own RBAC, so run the same command on each; a binding
on one does not apply to the other.&lt;/p>
&lt;p>&lt;strong>Terminal 1:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create clusterrolebinding cluster-admin-binding &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --clusterrole cluster-admin &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --user &lt;span class="k">$(&lt;/span>gcloud config get-value core/account&lt;span class="k">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Terminal 2:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create clusterrolebinding cluster-admin-binding &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --clusterrole cluster-admin &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --user &lt;span class="k">$(&lt;/span>gcloud config get-value core/account&lt;span class="k">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>If you see &lt;code>AlreadyExists&lt;/code> on either cluster, the binding was already created in a
previous session. This is not an error; continue to the next step.&lt;/p>&lt;/blockquote>
&lt;p>Verify on both clusters, each should return &lt;code>yes&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl auth can-i &lt;span class="s1">'*'&lt;/span> &lt;span class="s1">'*'&lt;/span> --all-namespaces &lt;span class="c1"># Terminal 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl auth can-i &lt;span class="s1">'*'&lt;/span> &lt;span class="s1">'*'&lt;/span> --all-namespaces &lt;span class="c1"># Terminal 2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h2 id="step-9-create-namespace-and-install-the-operator-on-both-clusters">Step 9: Create namespace and install the Operator on both clusters&lt;/h2>
&lt;p>The namespace &lt;strong>must be identical on both clusters&lt;/strong>. The MCS DNS name includes
the namespace (e.g. &lt;code>rs0.psmdb.svc.clusterset.local&lt;/code>). If the namespaces differ,
nodes cannot find each other.&lt;/p>
&lt;p>&lt;strong>Terminal 1 (main cluster):&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create namespace psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl config set-context --current --namespace&lt;span class="o">=&lt;/span>psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply --server-side &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.20.1/deploy/bundle.yaml &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Terminal 2 (replica cluster):&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create namespace psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl config set-context --current --namespace&lt;span class="o">=&lt;/span>psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply --server-side &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.20.1/deploy/bundle.yaml &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify the Operator is running on each cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Terminal 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-server-mongodb-operator-6877fcf797-stv4s 1/1 Running &lt;span class="m">0&lt;/span> 33s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Terminal 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running &lt;span class="m">0&lt;/span> 9s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-10-create-the-main-cluster">Step 10: Create the Main cluster&lt;/h2>
&lt;p>Run all commands in &lt;strong>Terminal 1&lt;/strong> (main cluster).&lt;/p>
&lt;p>Create &lt;code>cr-main.yaml&lt;/code>:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Important notes:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>type: ClusterIP&lt;/code> is &lt;strong>required&lt;/strong> for MCS, LoadBalancer will not work&lt;/li>
&lt;li>&lt;code>multiCluster.DNSSuffix: svc.clusterset.local&lt;/code> enables cross-cluster DNS&lt;/li>
&lt;li>&lt;code>crVersion: 1.20.1&lt;/code>, use a released version only. The Operator derives the
init container image tag from &lt;code>crVersion&lt;/code>.&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat > cr-main.yaml &lt;span class="s">&lt;&lt; 'EOF'
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: psmdb.percona.com/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: PerconaServerMongoDB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: main-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> crVersion: 1.20.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> updateStrategy: SmartUpdate
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> multiCluster:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> DNSSuffix: svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> upgradeOptions:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> apply: disabled
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> schedule: "0 2 * * *"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> secrets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> users: my-cluster-name-secrets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> replsets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - name: rs0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sharding:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> configsvrReplSet:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> mongos:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-main.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Watch until status is &lt;code>ready&lt;/code> (takes 3–5 minutes):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get psmdb -n psmdb -w&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get psmdb -n psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME ENDPOINT STATUS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster main-cluster-mongos.psmdb.svc.cluster.local:27017 ready 13m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify ServiceExport resources were created (takes up to 5 minutes after ready):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get serviceexport -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">NAME AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-0 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-1 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-2 26m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-mongos 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-0 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-1 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-2 26m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-11-export-secrets-from-the-main-cluster">Step 11: Export secrets from the Main cluster&lt;/h2>
&lt;p>Run all commands in &lt;strong>Terminal 1&lt;/strong> (main cluster).&lt;/p>
&lt;p>The Replica cluster runs in &lt;code>unmanaged: true&lt;/code> mode and cannot generate its own
TLS certificates or credentials. It must receive exact copies of the Main cluster secrets:&lt;/p>
&lt;ul>
&lt;li>Without TLS secrets → pods never start&lt;/li>
&lt;li>Without user credentials → pods start but fail liveness checks and restart continuously&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-32">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get secret my-cluster-name-secrets -n psmdb -o yaml > my-cluster-secrets.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get secret main-cluster-ssl -n psmdb -o yaml > main-cluster-ssl.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get secret main-cluster-ssl-internal -n psmdb -o yaml > main-cluster-ssl-internal.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get secret my-cluster-name-mongodb-encryption-key -n psmdb -o yaml > &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>my-cluster-name-mongodb-encryption-key.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-12-modify-secrets-for-the-replica-cluster">Step 12: Modify secrets for the Replica cluster&lt;/h2>
&lt;p>The exported secrets contain cluster-specific metadata that must be removed before
applying to another cluster. The &lt;code>resourceVersion&lt;/code> and &lt;code>uid&lt;/code> fields are unique to the
Main cluster and cause a conflict error if reused unchanged.&lt;/p>
&lt;p>The secret &lt;strong>data&lt;/strong> (passwords, TLS certificates, encryption key) is copied as-is,
the replica must use the same credentials to join the same MongoDB deployment. The
Kubernetes secret &lt;strong>names&lt;/strong> for user credentials and the encryption key stay the same
(&lt;code>my-cluster-name-secrets&lt;/code>, &lt;code>my-cluster-name-mongodb-encryption-key&lt;/code>) because
&lt;code>cr-replica.yaml&lt;/code> references those exact names. Only the TLS secrets are renamed
(&lt;code>main-cluster-ssl&lt;/code> → &lt;code>replica-cluster-ssl&lt;/code>) via &lt;code>sed&lt;/code>; the &lt;code>yq&lt;/code> step strips stale
metadata, it does not rename those two secrets.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Linux vs macOS:&lt;/strong> &lt;code>sed -i ''&lt;/code> is macOS-only syntax.
On Linux, use &lt;code>sed -i&lt;/code> without the empty string argument.&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>Terminal 1 (main cluster)&lt;/strong>, modify the exported files locally:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-33">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Secret 1, user credentials&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">yq &lt;span class="nb">eval&lt;/span> &lt;span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.selfLink, .metadata.uid)'&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> my-cluster-secrets.yml > my-cluster-secrets-replica.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">'s/main-cluster/replica-cluster/g'&lt;/span> my-cluster-secrets-replica.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Secret 2, SSL client certificates&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">yq &lt;span class="nb">eval&lt;/span> &lt;span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.selfLink, .metadata.uid)'&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> main-cluster-ssl.yml > replica-cluster-ssl.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">'s/main-cluster/replica-cluster/g'&lt;/span> replica-cluster-ssl.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Secret 3, SSL internal replication certificates&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">yq &lt;span class="nb">eval&lt;/span> &lt;span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.selfLink, .metadata.uid)'&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> main-cluster-ssl-internal.yml > replica-cluster-ssl-internal.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">'s/main-cluster/replica-cluster/g'&lt;/span> replica-cluster-ssl-internal.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Secret 4, encryption key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">yq &lt;span class="nb">eval&lt;/span> &lt;span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> .metadata.selfLink, .metadata.uid)'&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> my-cluster-name-mongodb-encryption-key.yml > &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> my-cluster-name-mongodb-encryption-key-replica.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">'s/main-cluster/replica-cluster/g'&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> my-cluster-name-mongodb-encryption-key-replica.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>Important:&lt;/strong> If you delete and recreate the Main cluster, re-export all four
secrets before applying to the Replica. The &lt;code>resourceVersion&lt;/code> and &lt;code>uid&lt;/code> change
on every cluster recreation, stale values cause a conflict error.&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>Terminal 2 (replica cluster)&lt;/strong>, apply and verify:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-34" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-34">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f my-cluster-secrets-replica.yaml -n psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -f replica-cluster-ssl.yml -n psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -f replica-cluster-ssl-internal.yml -n psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -f my-cluster-name-mongodb-encryption-key-replica.yml -n psmdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get secrets -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output should include:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-35" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-35">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">NAME TYPE DATA AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-cluster-name-mongodb-encryption-key Opaque 1 8s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-cluster-name-secrets Opaque 10 33s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-ssl kubernetes.io/tls 3 24s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-ssl-internal kubernetes.io/tls 3 16s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-13-create-the-replica-cluster">Step 13: Create the Replica cluster&lt;/h2>
&lt;p>Run all commands in &lt;strong>Terminal 2&lt;/strong> (replica cluster).&lt;/p>
&lt;p>Create &lt;code>cr-replica.yaml&lt;/code>:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Key differences from cr-main.yaml:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>unmanaged: true&lt;/code> prevents the Operator from initializing a new replica set,
avoiding split-brain with the Main cluster’s Operator&lt;/li>
&lt;li>&lt;code>updateStrategy: RollingUpdate&lt;/code>, SmartUpdate is not supported on unmanaged clusters&lt;/li>
&lt;li>SSL secrets are explicitly referenced because the Operator does not generate them here&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-36" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-36">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat > cr-replica.yaml &lt;span class="s">&lt;&lt; 'EOF'
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: psmdb.percona.com/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: PerconaServerMongoDB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: replica-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> unmanaged: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> crVersion: 1.20.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> updateStrategy: RollingUpdate
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> multiCluster:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> DNSSuffix: svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> upgradeOptions:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> apply: disabled
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> schedule: "0 2 * * *"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> secrets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> users: my-cluster-name-secrets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> ssl: replica-cluster-ssl
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sslInternal: replica-cluster-ssl-internal
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> replsets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - name: rs0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sharding:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> configsvrReplSet:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> mongos:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-37" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-37">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-replica.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Watch until status is &lt;code>ready&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-38" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-38">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get psmdb -n psmdb -w&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-39" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-39">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running &lt;span class="m">0&lt;/span> 119m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-0 1/1 Running &lt;span class="m">11&lt;/span> &lt;span class="o">(&lt;/span>25s ago&lt;span class="o">)&lt;/span> 43m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-1 1/1 Running &lt;span class="m">10&lt;/span> &lt;span class="o">(&lt;/span>7m49s ago&lt;span class="o">)&lt;/span> 43m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-2 1/1 Running &lt;span class="m">10&lt;/span> &lt;span class="o">(&lt;/span>7m25s ago&lt;span class="o">)&lt;/span> 42m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-mongos-0 0/1 Running &lt;span class="m">10&lt;/span> &lt;span class="o">(&lt;/span>6m46s ago&lt;span class="o">)&lt;/span> 42m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-0 1/1 Running &lt;span class="m">11&lt;/span> &lt;span class="o">(&lt;/span>22s ago&lt;span class="o">)&lt;/span> 43m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-1 1/1 Running &lt;span class="m">10&lt;/span> &lt;span class="o">(&lt;/span>7m17s ago&lt;span class="o">)&lt;/span> 43m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-2 1/1 Running &lt;span class="m">10&lt;/span> &lt;span class="o">(&lt;/span>7m21s ago&lt;span class="o">)&lt;/span> 42m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-40" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-40">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running &lt;span class="m">0&lt;/span> 113m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-0 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>108s ago&lt;span class="o">)&lt;/span> 37m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-1 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>72s ago&lt;span class="o">)&lt;/span> 36m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-2 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>48s ago&lt;span class="o">)&lt;/span> 36m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-mongos-0 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>9s ago&lt;span class="o">)&lt;/span> 36m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-0 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>104s ago&lt;span class="o">)&lt;/span> 37m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-1 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>40s ago&lt;span class="o">)&lt;/span> 36m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-2 0/1 CrashLoopBackOff &lt;span class="m">9&lt;/span> &lt;span class="o">(&lt;/span>44s ago&lt;span class="o">)&lt;/span> 36m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>Expected behavior before interconnect (Step 15):&lt;/strong> The replica cluster runs with
&lt;code>unmanaged: true&lt;/code>, so the Operator starts mongoc pods but does &lt;strong>not&lt;/strong> initialize a
separate replica set, that happens on the main cluster after you add &lt;code>externalNodes&lt;/code>
in Step 15. While waiting, replica pods may show &lt;code>CrashLoopBackOff&lt;/code> with many
restarts. This is usually the liveness probe timing out, not mongoc crashing. It is
common for &lt;code>cfg&lt;/code> and &lt;code>rs0&lt;/code> pods to settle to &lt;code>1/1 Running&lt;/code> before interconnect;
&lt;code>mongos&lt;/code> often stays &lt;code>0/1&lt;/code> the longest. &lt;code>kubectl get psmdb&lt;/code> may not show &lt;code>ready&lt;/code>
yet, that is expected. Continue to Steps 14 and 15.
If pods keep restarting &lt;strong>after&lt;/strong> Step 15, re-check the secrets from Steps 11–12.&lt;/p>&lt;/blockquote>
&lt;p>Verify ServiceExport resources were created (takes up to 5 minutes after ready):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-41" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-41">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get serviceexport -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-42" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-42">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">NAME AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-0 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-1 58m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-2 58m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-mongos 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-0 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-1 58m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-2 57m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-14-verify-serviceimports-on-both-clusters">Step 14: Verify ServiceImports on both clusters&lt;/h2>
&lt;p>After both clusters are running, the MCS controller creates &lt;code>ServiceImport&lt;/code> objects
automatically. This takes approximately 5 minutes after the ServiceExports appear.&lt;/p>
&lt;p>&lt;strong>Terminal 1 (main cluster):&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-43" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-43">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get serviceimport -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Terminal 2 (replica cluster):&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-44" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-44">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get serviceimport -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Each cluster should show &lt;strong>18 total ServiceImports&lt;/strong>, 9 for each cluster.
Example output on the replica cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-45" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-45">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">NAME TYPE IP AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg Headless 127m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-0 ClusterSetIP ["34.118.239.158"] 127m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-1 ClusterSetIP ["34.118.230.45"] 125m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-cfg-2 ClusterSetIP ["34.118.237.3"] 123m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-mongos ClusterSetIP ["34.118.230.127"] 127m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0 Headless 127m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-0 ClusterSetIP ["34.118.237.28"] 127m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-1 ClusterSetIP ["34.118.230.37"] 125m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main-cluster-rs0-2 ClusterSetIP ["34.118.226.30"] 123m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg Headless 62m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-0 ClusterSetIP ["34.118.231.166"] 62m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-1 ClusterSetIP ["34.118.234.146"] 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-cfg-2 ClusterSetIP ["34.118.225.208"] 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-mongos ClusterSetIP ["34.118.239.237"] 62m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0 Headless 62m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-0 ClusterSetIP ["34.118.228.53"] 62m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-1 ClusterSetIP ["34.118.238.50"] 59m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica-cluster-rs0-2 ClusterSetIP ["34.118.232.241"] 59m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If any are missing, check the MCS importer logs on the affected cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-46" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-46">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl logs -n gke-mcs -l k8s-app&lt;span class="o">=&lt;/span>gke-mcs-importer --tail&lt;span class="o">=&lt;/span>&lt;span class="m">30&lt;/span> &lt;span class="c1"># run in Terminal 1 or 2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-15-interconnect-the-clusters-add-externalnodes">Step 15: Interconnect the clusters (add externalNodes)&lt;/h2>
&lt;p>&lt;code>ServiceImport&lt;/code> objects give each cluster a way to resolve DNS names for services
in other clusters. &lt;code>externalNodes&lt;/code> tells MongoDB to actually use those addresses
as replica set members. Both are needed, ServiceImport is the phone book,
externalNodes is the instruction to call.&lt;/p>
&lt;p>&lt;strong>Why two voting and one non-voting external node?&lt;/strong>
Adding two voting nodes (&lt;code>votes: 1&lt;/code>) and one non-voting node (&lt;code>votes: 0&lt;/code>) from the
other site prevents split-brain. If the network between sites is severed, neither
side can accidentally promote a new Primary using only its external nodes.&lt;/p>
&lt;h3 id="15a-add-replica-nodes-to-main-cluster">15a: Add Replica nodes to Main cluster&lt;/h3>
&lt;p>Run in &lt;strong>Terminal 1&lt;/strong> (main cluster).&lt;/p>
&lt;p>Copy &lt;code>cr-main.yaml&lt;/code> to &lt;code>cr-main-after.yaml&lt;/code> and add an &lt;strong>&lt;code>externalNodes&lt;/code>&lt;/strong> block under
&lt;code>replsets.rs0&lt;/code> and under &lt;code>sharding.configsvrReplSet&lt;/code>, everything else stays the same.&lt;/p>
&lt;p>Create &lt;code>cr-main-after.yaml&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-47" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-47">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat > cr-main-after.yaml &lt;span class="s">&lt;&lt; 'EOF'
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: psmdb.percona.com/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: PerconaServerMongoDB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: main-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> crVersion: 1.20.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> updateStrategy: SmartUpdate
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> multiCluster:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> DNSSuffix: svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> upgradeOptions:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> apply: disabled
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> schedule: "0 2 * * *"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> secrets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> users: my-cluster-name-secrets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> replsets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - name: rs0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> externalNodes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-rs0-0.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-rs0-1.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-rs0-2.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sharding:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> configsvrReplSet:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> externalNodes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-cfg-0.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-cfg-1.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: replica-cluster-cfg-2.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> mongos:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-48" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-48">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-main-after.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="15b-add-main-nodes-to-replica-cluster">15b: Add Main nodes to Replica cluster&lt;/h3>
&lt;p>Run in &lt;strong>Terminal 2&lt;/strong> (replica cluster).&lt;/p>
&lt;p>Copy &lt;code>cr-replica.yaml&lt;/code> to &lt;code>cr-replica-after.yaml&lt;/code> and add an &lt;strong>&lt;code>externalNodes&lt;/code>&lt;/strong> block under
&lt;code>replsets.rs0&lt;/code> and under &lt;code>sharding.configsvrReplSet&lt;/code>, everything else stays the same.&lt;/p>
&lt;p>Create &lt;code>cr-replica-after.yaml&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-49" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-49">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat > cr-replica-after.yaml &lt;span class="s">&lt;&lt; 'EOF'
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: psmdb.percona.com/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: PerconaServerMongoDB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: replica-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> unmanaged: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> crVersion: 1.20.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> updateStrategy: RollingUpdate
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> multiCluster:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> DNSSuffix: svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> upgradeOptions:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> apply: disabled
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> schedule: "0 2 * * *"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> secrets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> users: my-cluster-name-secrets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> ssl: replica-cluster-ssl
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sslInternal: replica-cluster-ssl-internal
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> replsets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - name: rs0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> externalNodes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-rs0-0.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-rs0-1.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-rs0-2.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> sharding:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> configsvrReplSet:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> externalNodes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-cfg-0.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-cfg-1.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - host: main-cluster-cfg-2.psmdb.svc.clusterset.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> votes: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> priority: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> volumeSpec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> persistentVolumeClaim:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resources:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> requests:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> storage: 3Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> mongos:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> expose:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-50" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-50">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-replica-after.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>After interconnect:&lt;/strong> Pods may restart on both clusters while MongoDB reconfigures
the replica sets, brief &lt;code>CrashLoopBackOff&lt;/code> on replica is normal. Wait until all
pods are &lt;code>1/1 Running&lt;/code> before continuing to Step 16.&lt;/p>&lt;/blockquote>
&lt;h2 id="step-16-verify-cross-cluster-replication">Step 16: Verify cross-cluster replication&lt;/h2>
&lt;p>Run in &lt;strong>Terminal 1&lt;/strong> (main cluster).&lt;/p>
&lt;p>Get the clusterAdmin password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-51" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-51">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get secret my-cluster-name-secrets &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -n psmdb &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -o &lt;span class="nv">jsonpath&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"{.data.MONGODB_CLUSTER_ADMIN_PASSWORD}"&lt;/span> &lt;span class="p">|&lt;/span> base64 --decode&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Connect to the main cluster config server:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-52" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-52">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl &lt;span class="nb">exec&lt;/span> -it main-cluster-cfg-0 -n psmdb -- /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Inside the pod:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-53" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-53">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mongosh admin -u clusterAdmin -p &lt;password-from-above>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check replica set members:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">javascript&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-54" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-54">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">members&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output, 6 members total, all using &lt;code>svc.clusterset.local&lt;/code> DNS names:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">javascript&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-55" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-55">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">cfg&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">direct&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">primary&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nx">admin&lt;/span>&lt;span class="o">>&lt;/span> &lt;span class="nx">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">members&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'PRIMARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">17202&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionTime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Timestamp&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">t&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1780921358&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionDate&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">ISODate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'2026-06-08T12:22:38.000Z'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">self&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">17034&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">16861&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3214&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3181&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-2.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3164&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If all 6 members appear with &lt;code>health: 1&lt;/code>, cross-cluster replication is working.&lt;/p>
&lt;h2 id="step-17-test-the-switchover-process">Step 17: Test the switchover process&lt;/h2>
&lt;p>In a multi-cluster deployment, &lt;strong>only one Operator should actively manage the replica
set at a time&lt;/strong>, otherwise both sites could try to reconfigure MongoDB and cause
split-brain.&lt;/p>
&lt;p>Until now, the &lt;strong>main&lt;/strong> Operator was in charge (&lt;code>unmanaged&lt;/code> not set, so managed by
default). The &lt;strong>replica&lt;/strong> Operator only kept pods running (&lt;code>unmanaged: true&lt;/code>) and
did not drive failover or replica-set changes.&lt;/p>
&lt;p>This step simulates a site failover in two moves:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Main → unmanaged&lt;/strong>: main Operator stops managing the replica set.&lt;/li>
&lt;li>&lt;strong>Replica → managed&lt;/strong>: replica Operator takes over and can elect a new PRIMARY.&lt;/li>
&lt;/ol>
&lt;p>Apply both changes below, then verify MongoDB elects a new PRIMARY on the replica side.&lt;/p>
&lt;p>&lt;strong>Terminal 1 (main cluster)&lt;/strong>, release Operator control on main:&lt;/p>
&lt;p>Edit &lt;code>cr-main-after.yaml&lt;/code> under &lt;code>spec:&lt;/code>, add &lt;code>unmanaged: true&lt;/code> and change
&lt;code>updateStrategy&lt;/code> from &lt;code>SmartUpdate&lt;/code> to &lt;code>RollingUpdate&lt;/code> (SmartUpdate requires a
managed cluster):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-56" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-56">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">unmanaged&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">updateStrategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RollingUpdate&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-57" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-57">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-main-after.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Terminal 2 (replica cluster)&lt;/strong>, give Operator control on replica:&lt;/p>
&lt;p>Edit &lt;code>cr-replica-after.yaml&lt;/code> under &lt;code>spec:&lt;/code>, change &lt;code>unmanaged: true&lt;/code> to
&lt;code>unmanaged: false&lt;/code> so the replica Operator can manage failover and replica-set
reconfiguration:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-58" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-58">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">unmanaged&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-59" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-59">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f cr-replica-after.yaml -n psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify a new PRIMARY was elected on the replica side (&lt;strong>Terminal 2&lt;/strong>):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-60" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-60">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl &lt;span class="nb">exec&lt;/span> -it replica-cluster-cfg-0 -n psmdb -- /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Inside the pod:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-61" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-61">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mongosh admin -u clusterAdmin -p &lt;password-from-step-16>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">javascript&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-62" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-62">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">members&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected: &lt;code>replica-cluster-cfg-0&lt;/code> is PRIMARY, main-side members are SECONDARY:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">javascript&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-63" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-63">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">19106&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">self&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">18938&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">18765&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'PRIMARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5118&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionTime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Timestamp&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">t&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1780940264&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionDate&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">ISODate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'2026-06-08T17:37:44.000Z'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5085&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-2.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">health&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stateStr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'SECONDARY'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">uptime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5068&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pingMs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Long&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0'&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastHeartbeatMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceHost&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">syncSourceId&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">infoMessage&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">''&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configVersion&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configTerm&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-18-cleanup">Step 18: Cleanup&lt;/h2>
&lt;p>To remove the GKE clusters when you are done:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-64" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-64">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud container clusters delete main-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --zone us-central1-a &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --quiet
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcloud container clusters delete replica-cluster &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --zone us-central1-a &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --quiet&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/blog/deploying-percona-operator-for-mongodb-across-gke-clusters-with-mcs/" target="_blank" rel="noopener noreferrer">Original blog post by Ivan Groenewold&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/replication-mcs.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB, Multi-Cluster Services&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/multi-cluster-services" target="_blank" rel="noopener noreferrer">GKE Multi-Cluster Services overview&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/replication-mcs-gke.html" target="_blank" rel="noopener noreferrer">GKE MCS setup, Percona docs&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mongodb.com/docs/manual/core/replica-set-elections/" target="_blank" rel="noopener noreferrer">MongoDB replica set elections&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md" target="_blank" rel="noopener noreferrer">Kubernetes MCS API KEP-1645&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Edith Puclla</author><author>Ivan Groenewold</author><category>Kubernetes</category><category>Operators</category><category>Open Source</category><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2026/06/mcs_hu_8cd54e88cb67db6c.jpg"/><media:content url="https://percona.community/blog/2026/06/mcs_hu_6adc49f32e9e1e80.jpg" medium="image"/></item><item><title>The Percona Community Slack is open — come hang out</title><link>https://percona.community/blog/2026/06/02/percona-community-slack-open/</link><guid>https://percona.community/blog/2026/06/02/percona-community-slack-open/</guid><pubDate>Tue, 02 Jun 2026 11:00:00 UTC</pubDate><description>The Percona Community Slack is open — come hang out There’s a new place for the people behind the databases to actually talk to each other.</description><content:encoded>&lt;h1 id="the-percona-community-slack-is-open--come-hang-out">The Percona Community Slack is open — come hang out&lt;/h1>
&lt;p>There’s a new place for the people behind the databases to actually talk to each other.&lt;/p>
&lt;p>The Percona Community Slack is open. Right now it’s one channel — General — and that’s intentional. It’s a place for DBAs, developers, contributors, and database people of all kinds to meet, swap stories, and get to know who else is out there running open source databases for a living. No silos. No sub-channels for every topic. Just a room.&lt;/p>
&lt;h2 id="what-its-for">What it’s for&lt;/h2>
&lt;p>Come here to talk shop. Share what you’re building, breaking, or fixing. Post about the migration that went sideways, the config that finally clicked, the pager incident you survived. Ask the kind of questions that belong in a conversation rather than a ticket — “how do other people handle X?” is exactly the right energy.&lt;/p>
&lt;p>It’s also where we’ll share events we’re attending and, when we have tickets or a spare seat, offer them to the community first. If Percona is heading to a conference near you, this is where you’ll hear about it. And if you’re going somewhere yourself — a meetup, a conference, a local user group — tell us. There might be community members nearby who want to meet up.&lt;/p>
&lt;p>That’s the point, really. Less broadcast, more conversation.&lt;/p>
&lt;h2 id="what-its-not-for">What it’s not for&lt;/h2>
&lt;p>Technical support questions belong on the &lt;a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a>. Forum answers are searchable and don’t disappear into scrollback. Percona engineers and experienced community members watch the forums for questions. Your problem is more likely to get a useful answer there — and it’ll help the person who hits the same issue three months from now.&lt;/p>
&lt;p>If you post a support question in Slack, expect to be pointed to the forums. That’s not a brush-off.&lt;/p>
&lt;h2 id="a-few-things-that-make-this-work">A few things that make this work&lt;/h2>
&lt;p>&lt;strong>Introduce yourself.&lt;/strong> One or two sentences about what you work on and where in the world you are. That’s it. You don’t need a bio.&lt;/p>
&lt;p>&lt;strong>Share what you’re up to.&lt;/strong> An event you’re going to, a tool you’ve been testing, a war story from production. The low-key post about a thing you just dealt with is exactly what people come here for.&lt;/p>
&lt;p>&lt;strong>Lurk freely.&lt;/strong> You don’t have to post to belong. Read, learn, jump in when you have something to say.&lt;/p>
&lt;h2 id="the-short-version-of-the-rules">The short version of the rules&lt;/h2>
&lt;p>Be the person you’d want to share an on-call rotation with.&lt;/p>
&lt;p>Treat everyone as a peer. Assume good faith. No harassment. Critique technology on technical merits. Don’t cold-DM people with pitches. Keep private things private. If something needs a moderator’s attention, DM one directly — reports stay confidential.&lt;/p>
&lt;h2 id="come-in">Come in&lt;/h2>
&lt;p>If you’re a DBA, a developer, a contributor, or just someone who runs databases and occasionally wants to talk to other people who run databases — you belong here.&lt;/p>
&lt;p>&lt;a href="https://join.slack.com/t/percona/shared_invite/zt-3zqzw80xz-864PxCOIiiilYSVMnoN5ow" target="_blank" rel="noopener noreferrer">Join the Percona Community Slack →&lt;/a>&lt;/p></content:encoded><author>Laura Czajkowski</author><category>Community</category><category>Percona</category><category>Open Source</category><category>MySQL</category><category>PostgreSQL</category><category>MongoDB</category><category>MariaDB</category><category>Valkey</category><category>Slack</category><media:thumbnail url="https://percona.community/blog/2026/06/laura_blog_cover_hu_bc9f4c8e37f937bb.jpg"/><media:content url="https://percona.community/blog/2026/06/laura_blog_cover_hu_56be4b76a1d36710.jpg" medium="image"/></item><item><title>Building Smart Semantic Search using PostgreSQL and pgvector. Case Study - Part 2 - Postgres Layer</title><link>https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/</link><guid>https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/</guid><pubDate>Sun, 31 May 2026 11:00:00 UTC</pubDate><description>I’ll explain how I built the Postgres layer for semantic vector search on the Percona Community website: pgvector, chunks, two table modifications, the database schema, how the indexer populates Postgres, and what the SELECT statement looks like during a search.</description><content:encoded>&lt;p>I’ll explain how I built the &lt;strong>Postgres layer&lt;/strong> for semantic vector search on the Percona Community website: pgvector, chunks, two table modifications, the database schema, how the indexer populates Postgres, and &lt;strong>what the SELECT statement looks like during a search&lt;/strong>.&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1&lt;/a>: why semantic search, what’s already working on the site, the widget, and an overview of the stack.&lt;/p>&lt;/blockquote>
&lt;p>
&lt;figure>&lt;img width="2496" height="1708" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_f842e03eb46392bf.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_82814325dd28f4d8.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_40abd7714926c564.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_40abd7714926c564.webp" alt="Percona.community website search" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="architecture">Architecture&lt;/h2>
&lt;p>Search runs separately from the website at &lt;strong>search.percona.community&lt;/strong>: FastAPI, a background indexer, and PostgreSQL with pgvector are all in a single Docker Compose file. &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a> remains static on Hugo and GitHub Pages, it doesn’t write directly to the database.&lt;/p>
&lt;pre class="mermaid">
flowchart TB
subgraph users["Users"]
direction TB
User(["User"]) --> Widget["Widget · percona.community"]
Admin(["Admin"]) --> Dash["Admin dashboard · /demo"]
Site["Site · RSS + HTML"]
end
subgraph app["Application"]
direction TB
API["FastAPI · search.percona.community"]
Model["nomic-embed-text-v1"]
Worker["Indexer worker"]
API -->|embed query| Model
Model -->|embedding| API
Worker -->|embed chunks| Model
Model -->|embeddings| Worker
end
subgraph data["Database"]
direction TB
DB[("PostgreSQL + pgvector")]
end
Widget --> API
Dash --> API
Site --> Worker
API -->|read vectors · write queue/history| DB
Worker -->|write vectors · read queue| DB
&lt;/pre>
&lt;h3 id="search">Search&lt;/h3>
&lt;p>A visitor enters a query into the widget. The widget sends a &lt;code>POST /search&lt;/code> request to FastAPI. The service computes the query embedding with nomic with the prefix &lt;code>search_query:&lt;/code> and searches for the nearest vectors in Postgres. The widget knows nothing about pgvector, it only receives JSON with links.&lt;/p>
&lt;h3 id="admin-dashboard">Admin dashboard&lt;/h3>
&lt;p>On the same FastAPI service I run an admin dashboard at &lt;code>/demo&lt;/code>: test queries, search history, a database summary, viewing documents and chunks. The dashboard does not talk to Postgres directly, it only calls the API; the API reads and writes Postgres (&lt;code>search_history&lt;/code>, &lt;code>index_queue&lt;/code>, search results).&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2364" height="1706" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_9503b2dc4823a752.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_f02d96a9d57f766b.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_494ee04c729ede44.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_494ee04c729ede44.webp" alt="Admin dashboard - Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="indexing">Indexing&lt;/h3>
&lt;p>To refresh the index, I click &lt;strong>Start Indexing&lt;/strong> in the dashboard, that hits &lt;code>POST /index/start&lt;/code>. The same endpoint can be called from outside: a GitHub webhook after a push to the site repo, cron, or curl while debugging. FastAPI enqueues the job in &lt;code>index_queue&lt;/code>. A worker in the indexer container picks it up, downloads RSS and HTML from the site, splits text into chunks, computes vectors with nomic (&lt;code>search_document:&lt;/code>), and writes to &lt;code>pages&lt;/code>, &lt;code>community_nomic&lt;/code>, and &lt;code>indexer_runs&lt;/code>. The crawl runs in the background and does not block HTTP.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2262" height="1648" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_78eb347bd706e0c2.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_71c214d9127b6b91.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_45ebee06af7e8f31.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_45ebee06af7e8f31.webp" alt="Admin dashboard - Indexing" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Important limitation: the indexer and the API must use the same embedding model. The query vector and the vectors in the database must be from the same space, otherwise, cosine similarity doesn’t make sense.&lt;/p>
&lt;h2 id="pgvector-in-postgres">pgvector in Postgres&lt;/h2>
&lt;p>For semantic search, you don’t need an LLM, but an &lt;strong>embedding model&lt;/strong>: a string as input, a vector as output. I chose &lt;strong>nomic-embed-text-v1&lt;/strong>, 768-dimensional, running via &lt;code>sentence-transformers&lt;/code> on the CPU, without a paid API.&lt;/p>
&lt;p>I’m using &lt;strong>&lt;a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18&lt;/a>&lt;/strong>, pgvector is already included in the distribution; &lt;code>CREATE EXTENSION vector&lt;/code>, and you’re done (&lt;a href="https://docs.percona.com/postgresql/18/enable-extensions.html#pgvector" target="_blank" rel="noopener noreferrer">documentation&lt;/a>).&lt;/p>
&lt;p>The basic structure is a column with &lt;strong>fixed dimensions&lt;/strong> for the model:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">EXTENSION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">EXISTS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">chunks&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">SERIAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">PRIMARY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">KEY&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">TEXT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">768&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- exactly 768 under nomic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In the project, the table is named &lt;code>community_nomic&lt;/code>: the prefix &lt;code>community_&lt;/code> (site) + the model key &lt;code>nomic&lt;/code>. I’m setting up a comparison of embedding models: &lt;strong>each&lt;/strong> model has &lt;strong>its own&lt;/strong> vector table (&lt;code>community_&lt;model>&lt;/code>), because the dimensions and embedding spaces are different, so they can’t be mixed in a single table. Currently, there is one model in the project, &lt;strong>nomic-embed-text-v1&lt;/strong>, 768 dimensions; later, I can add a second table &lt;code>community_&lt;model_key>&lt;/code> and switch the index/API via &lt;code>EMBEDDING_MODEL_KEY&lt;/code>.&lt;/p>
&lt;p>pgvector compares vectors with several &lt;strong>distance operators&lt;/strong>. I search with &lt;strong>cosine distance&lt;/strong> (the &lt;code>&lt;=>&lt;/code> operator in SQL): the smaller the distance, the closer the match. In the widget and API I show &lt;strong>similarity&lt;/strong>, not the raw distance, &lt;code>similarity = 1 - distance&lt;/code>, so a higher score means a better hit. The operators:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Operator&lt;/th>
&lt;th>When useful&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>&lt;->&lt;/code>&lt;/td>
&lt;td>L2 (Euclidean)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&lt;#>&lt;/code>&lt;/td>
&lt;td>inner product&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&lt;=>&lt;/code>&lt;/td>
&lt;td>&lt;strong>cosine&lt;/strong>, my choice for nomic&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Simplified search for “nearest chunks”:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">community_nomic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LIMIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The threshold in the API is &lt;code>min_score&lt;/code> (my default is &lt;strong>0.52&lt;/strong>): anything lower is discarded. On beta I tuned this number for a while, the results changed noticeably depending on this single parameter.&lt;/p>
&lt;p>To avoid scanning the entire table as the index grows, I set up an &lt;strong>HNSW&lt;/strong> index (approximate nearest neighbor search):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">community_nomic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">USING&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">hnsw&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">vector_cosine_ops&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At this scale, a separate vector database wasn’t necessary, a single Postgres instance handles metadata, vectors, and search.&lt;/p>
&lt;h2 id="postgres-in-docker-docker-compose">Postgres in Docker: &lt;code>docker-compose&lt;/code>&lt;/h2>
&lt;p>I set up the stack using &lt;strong>Docker Compose&lt;/strong>, Postgres, the API, and the indexer are all in containers, with the same setup locally and in production. Production, &lt;strong>EC2 on AWS&lt;/strong> (&lt;code>search.percona.community&lt;/code>), an ARM instance, using the same &lt;code>docker-compose&lt;/code>.&lt;/p>
&lt;p>In &lt;code>docker-compose.yml&lt;/code>, Postgres on Percona looks like this (on Mac ARM):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">postgres&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-distribution-postgresql:18.1-3-arm64&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">community_search&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"5433:5432"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">pgdata:/var/lib/postgresql/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./init:/docker-entrypoint-initdb.d&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In &lt;code>init/01-enable-pgvector.sql&lt;/code>, include only &lt;code>CREATE EXTENSION IF NOT EXISTS vector&lt;/code>. If you’re developing on &lt;strong>x86&lt;/strong>, &lt;strong>use&lt;/strong> amd64 in the image tag instead of &lt;code>arm64&lt;/code>, see the options in the &lt;a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona documentation&lt;/a>. I left &lt;strong>arm64&lt;/strong> on both Mac and EC2: the configuration is the same.&lt;/p>
&lt;p>I view the tables and data in &lt;strong>pgAdmin&lt;/strong>. The &lt;code>pages&lt;/code>, &lt;code>community_nomic&lt;/code>, and service tables themselves are created when the API and indexer start using &lt;code>ensure_*&lt;/code> functions in the code: these are &lt;code>CREATE TABLE IF NOT EXISTS&lt;/code> and &lt;code>CREATE INDEX IF NOT EXISTS&lt;/code>, not a separate migration directory.&lt;/p>
&lt;h2 id="indexing-and-chunking">Indexing and Chunking&lt;/h2>
&lt;p>The site doesn’t write directly to the database, the database is populated by an &lt;strong>indexer&lt;/strong>: a worker fetches RSS and HTML from &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a>, splits the text into chunks, computes embeddings, and writes the rows to &lt;code>community_nomic&lt;/code> and &lt;code>pages&lt;/code>. The widget and API only read what has already been written during searches.&lt;/p>
&lt;h3 id="why-chunks">Why Chunks&lt;/h3>
&lt;p>At first, I tried &lt;strong>a single vector for the entire article&lt;/strong>. I quickly ran into three problems:&lt;/p>
&lt;ul>
&lt;li>Long text takes longer to encode and consumes more memory;&lt;/li>
&lt;li>The model has an input length limit;&lt;/li>
&lt;li>a single vector for long text &lt;strong>blurs&lt;/strong> the meaning, a query about a specific paragraph doesn’t map well to the “averaged” embedding of the entire article.&lt;/li>
&lt;/ul>
&lt;p>I settled on a &lt;strong>400-word&lt;/strong> window with a &lt;strong>50&lt;/strong>-word overlap (&lt;code>chunker.py&lt;/code>). Each chunk is a separate line with its own &lt;code>embedding&lt;/code>.&lt;/p>
&lt;p>The first version of the chunker sliced &lt;strong>only the body&lt;/strong> of the article, without the title, author, date, or tags. For queries like “articles by a certain author,” the results were off: the model saw the text but not the document’s context. I added &lt;strong>metadata to each chunk&lt;/strong>, a &lt;code>Title / Author / Date / Tags / Type&lt;/code> block at the beginning of each fragment before calculating the vector.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2068" height="1574" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_1c1f25f7b4b54713.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_b33eeed299f15563.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_448f58d17c5cf19c.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_448f58d17c5cf19c.webp" alt="Admin dashboard - Chunking" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>When searching, the API finds the closest chunks, but the card shows &lt;strong>the best chunk for the document&lt;/strong> (one &lt;code>slug&lt;/code>, one card). Without this, a long article would clutter the results with multiple lines.&lt;/p>
&lt;h2 id="database-schema-two-revisions-of-the-chunk-tables">Database Schema: Two Revisions of the Chunk Tables&lt;/h2>
&lt;p>I revised the chunk storage schema &lt;strong>twice&lt;/strong>, and separately added utility tables for background indexing and search logs.&lt;/p>
&lt;h3 id="version-1-everything-in-a-single-table">Version 1: Everything in a Single Table&lt;/h3>
&lt;p>The first working schema was &lt;strong>a single table for all chunks and document information&lt;/strong>: each row represented a single article fragment, and it also contained duplicated page metadata (&lt;strong>url, title, author, date, tags, content_type&lt;/strong>) along with &lt;code>chunk_text&lt;/code> and &lt;code>embedding&lt;/code>.&lt;/p>
&lt;p>Pros: one &lt;code>INSERT&lt;/code>, one &lt;code>SELECT&lt;/code>, no joins.&lt;/p>
&lt;p>Cons I encountered:&lt;/p>
&lt;ul>
&lt;li>one article, dozens of identical copies of title and author;&lt;/li>
&lt;li>when updating a page, it’s easy to get out of sync (one title in chunk #0, another in chunk #3);&lt;/li>
&lt;li>fetching the image and description for the card from &lt;code>chunk_text&lt;/code> was unreliable.&lt;/li>
&lt;/ul>
&lt;p>Conclusion: A &lt;strong>vector layer&lt;/strong> and a &lt;strong>card in the UI&lt;/strong> serve different purposes.&lt;/p>
&lt;p>The code still includes &lt;code>_migrate_chunks_table&lt;/code>: when the API and indexer start up (inside &lt;code>ensure_content_table&lt;/code>), it drops any extra columns from the chunk table if they are left over from the old prototype.&lt;/p>
&lt;h3 id="version-2-pages--community_nomic">Version 2: &lt;code>pages&lt;/code> + &lt;code>community_nomic&lt;/code>&lt;/h3>
&lt;p>I split the data into two tables:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>pages&lt;/code>&lt;/strong>, one row per document: url, title, type, author, date, tags, images, description.&lt;/li>
&lt;li>&lt;strong>&lt;code>community_nomic&lt;/code>&lt;/strong>, only chunks: slug, chunk_index, chunk_text, embedding.&lt;/li>
&lt;/ul>
&lt;p>They are linked by &lt;code>slug&lt;/code> (stable key from the URL). Search: find the nearest chunks in &lt;code>community_nomic&lt;/code>, assemble the card from &lt;code>pages&lt;/code>.&lt;/p>
&lt;p>In the admin dashboard I can open any indexed document and see what landed in &lt;code>pages&lt;/code> (metadata, image, description) and what text was split into chunks.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2234" height="1192" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_d0153d5b682f4556.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_94f1c78860508307.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_280bec26c6ad4f25.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_280bec26c6ad4f25.webp" alt="Admin dashboard, document details (pages) and chunks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>HNSW on &lt;code>community_nomic&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INDEX&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">community_nomic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">USING&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">hnsw&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">vector_cosine_ops&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That’s why, when switching models, I don’t reuse &lt;code>community_nomic&lt;/code>; instead, I create a new table and re-index it. A single search query involves vectors from &lt;strong>only one&lt;/strong> model, both during indexing and in the API.&lt;/p>
&lt;h2 id="indexer-rss-http-and-queue">Indexer: RSS, HTTP, and Queue&lt;/h2>
&lt;p>The indexer is a separate container that crawls &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a> and populates the database. It starts with &lt;strong>RSS&lt;/strong>, four feeds:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://percona.community/blog/index.xml" target="_blank" rel="noopener noreferrer">blog/index.xml&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/events/index.xml" target="_blank" rel="noopener noreferrer">events/index.xml&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/talks/index.xml" target="_blank" rel="noopener noreferrer">talks/index.xml&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/contributors/index.xml" target="_blank" rel="noopener noreferrer">contributors/index.xml&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>RSS feeds contain a title, link, date, author, tags, and often a short description, but &lt;strong>not the full text of the article&lt;/strong>. For each entry, I perform an &lt;strong>HTTP GET&lt;/strong> on the HTML page and extract the main content (in &lt;code>crawler.py&lt;/code>). If the HTML is empty, I fall back to the description from the RSS feed.&lt;/p>
&lt;p>The &lt;strong>Index&lt;/strong> and &lt;strong>Status&lt;/strong> tabs in the dashboard, without them, debugging the crawl and embedding would have been a guessing game.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2016" height="1622" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_2060d481735c81.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_5312a3fafb6f90fc.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_8d5557a28fba57a9.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_8d5557a28fba57a9.webp" alt="Admin dashboard - Index Running" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1986" height="690" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_442fc75f179d91c9.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_8ca30910677678ec.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_50a243ec8175dee5.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_50a243ec8175dee5.webp" alt="Admin dashboard - Index history" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2078" height="1324" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_9f7a9c5c2ee04ff6.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_52d980bc75a3d957.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_c4c215e93a9a0c6a.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_c4c215e93a9a0c6a.webp" alt="Admin dashboard - Index overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="table-schema">Table Schema&lt;/h2>
&lt;p>All tables are created when the API and indexer start (&lt;code>ensure_*&lt;/code> in code, &lt;code>CREATE TABLE IF NOT EXISTS&lt;/code>, &lt;code>CREATE INDEX IF NOT EXISTS&lt;/code>). There is no separate migrations folder. I don’t use foreign keys between search and utility tables: reindexing deletes and re-inserts rows by &lt;code>slug&lt;/code>, and the queue tables are only loosely linked.&lt;/p>
&lt;h3 id="search-data">Search data&lt;/h3>
&lt;p>&lt;strong>&lt;code>pages&lt;/code>&lt;/strong> and &lt;strong>&lt;code>community_nomic&lt;/code>&lt;/strong> are linked by &lt;code>slug&lt;/code> (no FK). The indexer writes both; the API reads them on &lt;code>POST /search&lt;/code>.&lt;/p>
&lt;h4 id="pages">pages&lt;/h4>
&lt;p>One row per document.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Column&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>slug&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>primary key, stable key from the URL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>url&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>canonical link (UNIQUE)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>content_type&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>blog, event, talk, contributor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>title&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>card title&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>date&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>publication date from RSS/HTML&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>author&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>author name&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tags&lt;/code>&lt;/td>
&lt;td>TEXT[]&lt;/td>
&lt;td>tags for search and chunk metadata&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>image_url&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>full image from the site&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>image_thumb_url&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>smaller image for the widget popup&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>description&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>short description for the card&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>updated_at&lt;/code>&lt;/td>
&lt;td>TIMESTAMPTZ&lt;/td>
&lt;td>last time the row was indexed&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="community_nomic">community_nomic&lt;/h4>
&lt;p>Chunks and vectors (table name = site + model key).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Column&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>SERIAL&lt;/td>
&lt;td>primary key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>slug&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>link to &lt;code>pages&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>chunk_index&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>chunk position in the document (UNIQUE with &lt;code>slug&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>chunk_text&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>text passed to the embedding model&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>embedding&lt;/code>&lt;/td>
&lt;td>vector(768)&lt;/td>
&lt;td>nomic vector for cosine search&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="utility">Utility&lt;/h3>
&lt;p>Three small tables for indexing and debugging.&lt;/p>
&lt;h4 id="index_queue">index_queue&lt;/h4>
&lt;p>Pending jobs. Written by the API.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Column&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>SERIAL&lt;/td>
&lt;td>primary key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>created_at&lt;/code>&lt;/td>
&lt;td>TIMESTAMPTZ&lt;/td>
&lt;td>when the job was queued&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>status&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>pending, running, done, cancelled&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>model&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>embedding model key (&lt;code>nomic&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>feeds&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>RSS feed URLs (comma-separated)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>crawl_delay&lt;/code>&lt;/td>
&lt;td>FLOAT&lt;/td>
&lt;td>pause between HTTP requests (seconds)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>limit_per_type&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>cap per content type (partial reindex)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>run_id&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>&lt;code>indexer_runs.id&lt;/code> once the worker starts&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>cancel_requested&lt;/code>&lt;/td>
&lt;td>BOOLEAN&lt;/td>
&lt;td>cancel flag from the dashboard&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="indexer_runs">indexer_runs&lt;/h4>
&lt;p>Crawl progress. Written by the indexer worker.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Column&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>SERIAL&lt;/td>
&lt;td>primary key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>started_at&lt;/code>&lt;/td>
&lt;td>TIMESTAMPTZ&lt;/td>
&lt;td>run start&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>finished_at&lt;/code>&lt;/td>
&lt;td>TIMESTAMPTZ&lt;/td>
&lt;td>run end&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>status&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>running, done, error, cancelled&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>model&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>embedding model key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>total_docs&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>documents processed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>total_chunks&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>chunks written&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>current_url&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>page being crawled&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>current_doc_num&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>document counter&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>errors&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>error count&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>message&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>status or error text&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="search_history">search_history&lt;/h4>
&lt;p>Search log. Written by the API on each &lt;code>POST /search&lt;/code>.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Column&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>SERIAL&lt;/td>
&lt;td>primary key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>created_at&lt;/code>&lt;/td>
&lt;td>TIMESTAMPTZ&lt;/td>
&lt;td>query time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>query&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>user query&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>content_type&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>filter: all or one type&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>limit_requested&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>requested result limit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>results_count&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>rows returned&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>chunks_in_index&lt;/code>&lt;/td>
&lt;td>INT&lt;/td>
&lt;td>snapshot: chunk count at query time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>by_type&lt;/code>&lt;/td>
&lt;td>JSONB&lt;/td>
&lt;td>hit counts per content type&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>prepare_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>API timing breakdown&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>model_load_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>model load time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>embed_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>embedding time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>db_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>Postgres search time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>format_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>JSON formatting time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>total_ms&lt;/code>&lt;/td>
&lt;td>REAL&lt;/td>
&lt;td>end-to-end time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>model&lt;/code>&lt;/td>
&lt;td>TEXT&lt;/td>
&lt;td>embedding model key&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="indexes">Indexes&lt;/h3>
&lt;p>Created in the same &lt;code>ensure_*&lt;/code> functions as the tables. Besides primary keys and &lt;code>UNIQUE&lt;/code> on &lt;code>pages.url&lt;/code> and &lt;code>(slug, chunk_index)&lt;/code> in &lt;code>community_nomic&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>pages_content_type_idx&lt;/code>&lt;/strong> on &lt;code>content_type&lt;/code>, filter by blog / event / talk / contributor in search;&lt;/li>
&lt;li>&lt;strong>&lt;code>community_nomic_embedding_idx&lt;/code>&lt;/strong>, &lt;strong>HNSW&lt;/strong> on &lt;code>embedding&lt;/code> (&lt;code>vector_cosine_ops&lt;/code>); without it, nearest-neighbor search would scan the whole table as chunks grow;&lt;/li>
&lt;li>&lt;strong>&lt;code>community_nomic_slug_idx&lt;/code>&lt;/strong> on &lt;code>slug&lt;/code>, delete all chunks for one document on reindex;&lt;/li>
&lt;li>&lt;strong>&lt;code>search_history_created_at_idx&lt;/code>&lt;/strong>, recent queries first in the dashboard History tab.&lt;/li>
&lt;/ul>
&lt;p>&lt;code>index_queue&lt;/code> and &lt;code>indexer_runs&lt;/code> only have a serial primary key, few rows, a full scan is fine.&lt;/p>
&lt;h2 id="how-postgres-responds-to-a-search-query">How Postgres Responds to a Search Query&lt;/h2>
&lt;p>The API receives the query text, computes a vector with nomic (&lt;code>search_query:&lt;/code> + text), and runs SQL that finds the nearest chunks and joins row metadata from &lt;code>pages&lt;/code>.&lt;/p>
&lt;h3 id="the-first-query-was-naive">The First Query Was Naive&lt;/h3>
&lt;p>At first, I did what the pgvector tutorials suggest, “find the 20 closest vectors”:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">chunk_index&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">community_nomic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LIMIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The query &lt;strong>worked&lt;/strong>, but the results were incorrect from a UI perspective. It returns &lt;strong>20 chunks&lt;/strong>, not &lt;strong>20 documents&lt;/strong>. A long article with fifteen chunks could take up &lt;strong>half the list&lt;/strong> with a single &lt;code>slug&lt;/code>; a short post with one good paragraph didn’t make it to the top. The user sees &lt;strong>pages&lt;/strong> (cards with links), but we search the database by &lt;strong>chunks&lt;/strong>, I close that gap in SQL.&lt;/p>
&lt;h3 id="what-i-do-now">What I do now&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>Join&lt;/strong> &lt;code>community_nomic&lt;/code> + &lt;code>pages&lt;/code> by &lt;code>slug&lt;/code>.&lt;/li>
&lt;li>&lt;code>ROW_NUMBER() PARTITION BY slug&lt;/code>, I keep &lt;strong>one&lt;/strong> best chunk per document.&lt;/li>
&lt;li>&lt;code>WHERE score >= min_score&lt;/code> (default &lt;strong>0.52&lt;/strong>).&lt;/li>
&lt;li>&lt;code>ORDER BY score DESC LIMIT N&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>Simplified version of the final query:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ranked&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_type&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ROW_NUMBER&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OVER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rn&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">community_nomic&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">INNER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pages&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">-- AND p.content_type = 'blog' -- optional: filter by type
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">best_per_page&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ranked&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rn&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">content_type&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">slug&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">best_per_page&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">52&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">score&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DESC&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LIMIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="filtering-by-content-type">Filtering by content type&lt;/h3>
&lt;p>The site has blog, event, talk, and contributor, in the widget and on &lt;code>/search/&lt;/code>, you can search for &lt;strong>all at once&lt;/strong> or a single type. In the API, this is the &lt;code>content_type&lt;/code> field in &lt;code>POST /search&lt;/code>; in SQL, &lt;code>AND p.content_type = %s&lt;/code> is added when a single type is selected.&lt;/p>
&lt;h3 id="sort-order-in-the-widget">Sort order in the widget&lt;/h3>
&lt;p>In SQL, results are ranked by similarity (&lt;code>ORDER BY score DESC&lt;/code>), “what matches the query best?”&lt;/p>
&lt;p>On a community site, &lt;strong>recent material often matters as much as the top semantic match&lt;/strong>. An older article might score 0.71 while a newer post on the same topic scores 0.66. I still build the shortlist in SQL (one best chunk per document, &lt;code>min_score&lt;/code> threshold), but the API then &lt;strong>re-sorts blog, event, and talk by publication date&lt;/strong>, newest first. Contributors and rows without a date stay at the bottom.&lt;/p>
&lt;p>The widget still shows the &lt;strong>similarity score&lt;/strong> on each card so you can see why the page was included:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2426" height="1700" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_8a12bbab9aea8be6.webp 768w, https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_c45c324ca8056aa8.webp 480w, https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_18e89fd6122bbc80.webp 1400w" src="https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_18e89fd6122bbc80.webp" alt="Widget search results with similarity scores" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>&lt;strong>Postgres layer&lt;/strong>: I set this up without a separate vector DB, using pgvector in Percona, two table modifications for chunking, auxiliary tables for background indexing, HNSW, and SQL with “best-fit chunk per document.” The indexer processes RSS and HTML; I manage the database in pgAdmin.&lt;/p>
&lt;p>Currently, the search index has about &lt;strong>803&lt;/strong> documents and &lt;strong>1,656&lt;/strong> vectors, thousands of rows, not billions. This is a community-scale setup: a single Postgres instance on EC2, embedding on the CPU, HNSW on all chunks, the solutions above were chosen with this in mind. When I add videos, GitHub issues, and the forum, the volume will grow, then I’ll re-evaluate the indexing time and hardware.&lt;/p>
&lt;h3 id="note-from-the-author">Note from the author&lt;/h3>
&lt;p>About &lt;strong>six months ago&lt;/strong>, I already tried to set up something similar to Postgres + vectors using AI agents. Back then, I kept running into the same issues: a clunky &lt;strong>startup&lt;/strong> of the environment, the &lt;strong>schema&lt;/strong> and its &lt;strong>modifications&lt;/strong>, &lt;strong>initializing Percona Distribution for PostgreSQL&lt;/strong>, and pgvector, the agent would either skip a step or suggest incompatible configuration snippets.&lt;/p>
&lt;p>This time, with &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a>, went better: the agent set up Compose, &lt;code>ensure_*&lt;/code>, search SQL, and the admin dashboard, without that series of failures at startup. More time was spent on the logic (chunks, &lt;code>min_score&lt;/code>, result ordering) rather than on “why the database won’t start.”&lt;/p>
&lt;p>If you try this setup yourself or notice any inaccuracies, please leave a comment.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>pgvector</category><category>search</category><category>embeddings</category><category>ai</category><media:thumbnail url="https://percona.community/blog/2026/05/search-part-2-cover_hu_46c8da5323a7e3b7.jpg"/><media:content url="https://percona.community/blog/2026/05/search-part-2-cover_hu_ce03bad6efcfc472.jpg" medium="image"/></item><item><title>Building Smart Semantic Search using PostgreSQL and pgvector. Case Study - Part 1 - Introduction</title><link>https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/</link><guid>https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/</guid><pubDate>Fri, 29 May 2026 11:00:00 UTC</pubDate><description>Type “zero downtime database migration” into the site’s search bar and you’ll get articles and talks about database migration with minimal downtime, even if those words aren’t in the titles or content. This is semantic search on PostgreSQL and pgvector, without paid embedding APIs or a separate vector database. In this series I’ll cover how it works and why I chose this stack.</description><content:encoded>&lt;p>Type “zero downtime database migration” into the site’s search bar and you’ll get articles and talks about database migration with minimal downtime, even if those words aren’t in the titles or content. This is &lt;strong>semantic search&lt;/strong> on &lt;strong>PostgreSQL&lt;/strong> and &lt;strong>&lt;a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector&lt;/a>&lt;/strong>, without paid embedding APIs or a separate vector database. In this series I’ll cover how it works and why I chose this stack.&lt;/p>
&lt;p>I’ll walk through how and why I built the search for our community site: blog, events, talks, and profiles. The post should help if you want to repeat the approach or need a practical case study on simple components. If you’ve done something similar, I’d like to hear your feedback.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2238" height="1698" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_6a0c54715619301d.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_42fb6b63071a7393.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_be512b221b02d108.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_be512b221b02d108.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Introduction" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="context-website-search-and-task">Context: Website, Search, and Task&lt;/h2>
&lt;p>The community team has a website on &lt;strong>Hugo&lt;/strong>, an open source static site generator, hosted for free on &lt;strong>GitHub Pages&lt;/strong>. The site has articles, events, talks, videos, and more.&lt;/p>
&lt;blockquote>
&lt;p>If you’re thinking of starting your own, I recommend checking out these examples: &lt;a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">blog.koehntopp.info&lt;/a>, &lt;a href="https://openeverest.io/" target="_blank" rel="noopener noreferrer">openeverest.io&lt;/a>, &lt;a href="https://perconalive.com/" target="_blank" rel="noopener noreferrer">perconalive.com&lt;/a>, &lt;a href="https://oursqlfoundation.org/" target="_blank" rel="noopener noreferrer">oursqlfoundation.org&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>But a Hugo site is a collection of HTML files without a backend. Search or filters only work via frontend JS or an external service. For a long time our site had no search at all. Then &lt;strong>Kai Wagner&lt;/strong> contributed a JS search for the blog that matched exact words (&lt;a href="https://percona.community/blog" target="_blank" rel="noopener noreferrer">percona.community/blog&lt;/a>).&lt;/p>
&lt;p>Recently our community lead &lt;strong>Laura Czajkowski&lt;/strong> asked for smart AI search on the site. We tried several off-the-shelf products; they were either too expensive or a poor fit. We also want search to cover more than the site itself eventually: videos from other platforms, the forum, our GitHub repos, and maybe documentation later.&lt;/p>
&lt;p>I suggested building it ourselves. Modern AI assistants are good enough for a prototype like this. Below I’ll explain the stack.&lt;/p>
&lt;h2 id="what-well-do">What We’ll Do&lt;/h2>
&lt;p>The site stays on Hugo and GitHub Pages. The search service runs &lt;strong>separately&lt;/strong>; for this architecture that’s the sensible option. The goal is simple: the user types a query in plain language and gets a list of semantically relevant links.&lt;/p>
&lt;p>Kai’s keyword search was a step forward, but it doesn’t catch &lt;strong>meaning&lt;/strong>. Type “postgresql” and you get pages where the word appears. An article about slow queries or replication may be missing if the wording is different. &lt;strong>Semantic search&lt;/strong> works differently: the query and documents become &lt;strong>vectors&lt;/strong>, numeric representations of meaning (&lt;strong>embedding&lt;/strong>). Similar meaning lands nearby in vector space even when the words differ. A query like “how to speed up slow queries in MySQL” can surface tuning and optimization content without those words in the title.&lt;/p>
&lt;p>Why not another engine? &lt;strong>&lt;a href="https://opensearch.org/" target="_blank" rel="noopener noreferrer">OpenSearch&lt;/a>&lt;/strong> is a solid open-source option: full-text and vector search, mature ecosystem. I also looked at &lt;strong>&lt;a href="https://manticoresearch.com/" target="_blank" rel="noopener noreferrer">Manticore Search&lt;/a>&lt;/strong>. Both work, but &lt;strong>semantics&lt;/strong> still need an embedding pipeline (model at index time and on each query). That’s another service to run beside the model.&lt;/p>
&lt;p>I wanted my own stack on &lt;strong>Postgres&lt;/strong> with pgvector: a practical experiment, not a hunt for the perfect search product. &lt;strong>PostgreSQL with &lt;a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector&lt;/a>&lt;/strong> keeps page metadata, chunks, vectors, and query history in one database. &lt;strong>&lt;a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18&lt;/a>&lt;/strong> ships pgvector in the distribution; run &lt;code>CREATE EXTENSION vector&lt;/code> and you’re set.&lt;/p>
&lt;p>The plan has four parts:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Widget&lt;/strong> on the site: search field and results (plain JS; Hugo unchanged).&lt;/li>
&lt;li>&lt;strong>API&lt;/strong>: takes the query, embeds it with the same model as indexing, searches the DB, returns JSON links.&lt;/li>
&lt;li>&lt;strong>Indexer&lt;/strong>: background worker that reads RSS/HTML, chunks text, embeds, writes to the DB.&lt;/li>
&lt;li>&lt;strong>PostgreSQL + pgvector&lt;/strong>: one database for metadata, chunks, vectors, and search history.&lt;/li>
&lt;/ol>
&lt;p>Hugo stays static; the smart parts live in a separate service. No separate vector DB, no paid embedding API, no RAG chat, only links.&lt;/p>
&lt;p>The diagram shows two flows: &lt;strong>search&lt;/strong> (user query) and &lt;strong>indexing&lt;/strong> (refresh the DB on demand or on a schedule). Top to bottom, from the user:&lt;/p>
&lt;pre class="mermaid">
flowchart TB
User(["👤 User"])
Widget["🔍 JS widget&lt;br/>percona.community · GitHub Pages"]
API["⚡ FastAPI&lt;br/>search.percona.community"]
Model["🧠 Embedding model&lt;br/>shared · API &amp; indexer"]
DB[("🗄️ PostgreSQL + pgvector")]
Content["📰 Content&lt;br/>blog · events · talks"]
Indexer["📥 Indexer worker"]
User -->|"① query"| Widget
Widget -->|"② POST /search"| API
API &lt;-->|embed query| Model
API &lt;-->|"③ vector search"| DB
API -->|"④ results"| Widget
Widget --> User
Content -->|"A. RSS + HTML"| Indexer
Indexer &lt;-->|embed chunks| Model
Indexer -->|"B. chunks + vectors"| DB
style User fill:#e1f5ff
style Widget fill:#fff4e6
style Content fill:#fff9e6
style API fill:#ffe6e6
style Model fill:#fff0f5
style Indexer fill:#f0e6ff
style DB fill:#e6ffe6
&lt;/pre>
&lt;p>The diagram shows the shared &lt;strong>embedding model&lt;/strong>; worth stating explicitly anyway. &lt;strong>The indexer and the API must use the same model.&lt;/strong> Query vectors and stored vectors must share one space or search is meaningless. Don’t mix Nomic at index time with OpenAI at query time, for example. The widget only sends text; it doesn’t know which model runs behind the API.&lt;/p>
&lt;p>On paper it looked simple. In practice I changed the database schema &lt;strong>three times&lt;/strong> and tuned ranking so blog posts didn’t crowd out events and talks. The &lt;strong>similarity threshold&lt;/strong> mattered more than I expected: one parameter, large swing in results. Still, within a few days we had a working beta on the live site. Here’s what shipped.&lt;/p>
&lt;h2 id="the-result-spoiler">The Result (Spoiler)&lt;/h2>
&lt;p>It took about &lt;strong>three unhurried days&lt;/strong> and roughly &lt;strong>$20 in Cursor tokens&lt;/strong> to build, debug, and deploy. Try it on &lt;strong>&lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a>&lt;/strong> (search icon in the header) or &lt;strong>&lt;a href="https://percona.community/search/" target="_blank" rel="noopener noreferrer">percona.community/search/&lt;/a>&lt;/strong>.&lt;/p>
&lt;p>The index currently covers the site: blog, events, talks, member profiles. Video from other platforms, the forum, and GitHub are planned; the design should allow new sources without replacing the stack.&lt;/p>
&lt;p>This is &lt;strong>beta&lt;/strong>: the content is public and search isn’t business-critical, but I watch stability and security.&lt;/p>
&lt;h3 id="website-widget">Website Widget&lt;/h3>
&lt;p>The header has a search icon. Click it to get an input field and a popup with results, &lt;strong>similarity score&lt;/strong> (0 to 1, how close the hit is in meaning), and API latency. The site stays static; the widget calls &lt;code>search.percona.community&lt;/code> and renders JSON. “All results” opens &lt;code>/search/&lt;/code>.&lt;/p>
&lt;p>Try it on &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a>, e.g. &lt;code>slow queries mysql tuning&lt;/code> or &lt;code>kubernetes operator database&lt;/code>. Comments welcome if something feels off.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2414" height="1696" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_e14bbb0b94fb8b99.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_d5010300b416bda2.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_1dcd2ecc4916d346.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_1dcd2ecc4916d346.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Widget" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="full-results-page">Full Results Page&lt;/h3>
&lt;p>A separate &lt;code>/search/&lt;/code> page with filters by content type, cards, and links.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2726" height="1714" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-page_hu_a44a460c39fd883d.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-page_hu_79a890728ec027bb.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-page_hu_ef00d7ba277d81ba.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-page_hu_ef00d7ba277d81ba.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Search Page" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://percona.community/search/?q=Postgres+backup+solutions&amp;type=blog" target="_blank" rel="noopener noreferrer">Example&lt;/a>&lt;/p>
&lt;h3 id="api">API&lt;/h3>
&lt;p>&lt;strong>FastAPI&lt;/strong> at &lt;code>https://search.percona.community&lt;/code>: embed the query, search Postgres, return JSON with links, scores, and timings (model vs database).&lt;/p>
&lt;p>The service runs on &lt;strong>AWS EC2&lt;/strong> in Docker Compose: API, indexer, Postgres.&lt;/p>
&lt;h3 id="demo-dashboard">Demo Dashboard&lt;/h3>
&lt;p>The Cursor AI agent handled a lot of the boilerplate, so I also built a &lt;strong>dev dashboard&lt;/strong> (&lt;code>/demo&lt;/code>) to test search, run indexing, inspect history, and browse indexed chunks. Not for production, but it saved debugging time.&lt;/p>
&lt;p>Demo Dashboard
&lt;figure>&lt;img width="2300" height="1616" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_581ac3d42e964d95.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_fd17f4982d7c6714.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_b171c38bfdc07801.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_b171c38bfdc07801.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Search history: making search better&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2190" height="1000" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_83a8f059471b04d9.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_33e76fef4e3c87a3.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_70c2a3d3114aef9f.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_70c2a3d3114aef9f.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search history" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Indexing status, to see when search data was last updated&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2434" height="1478" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_730bfa13896c3b7c.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_ce61d9cd4963de1b.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_8b567ed32a4500bb.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_8b567ed32a4500bb.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexing status" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Indexed documents with the ability to view data and chunks.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2486" height="1574" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_510e399fc64396b1.webp 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_eda8d00bcccab420.webp 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_f026d5ac3a4d0b12.webp 1400w" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_f026d5ac3a4d0b12.webp" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexed documents" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="what-i-used">What I Used&lt;/h3>
&lt;p>Briefly, &lt;strong>why&lt;/strong> this stack (deeper comparison in &lt;strong>part two&lt;/strong>):&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.org/" target="_blank" rel="noopener noreferrer">PostgreSQL&lt;/a>&lt;/strong> + &lt;strong>&lt;a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector&lt;/a>&lt;/strong>: vectors and metadata in one DB. Cosine similarity plus an HNSW index is enough at community scale. (&lt;a href="https://docs.percona.com/postgresql/18/enable-extensions.html#pgvector" target="_blank" rel="noopener noreferrer">pgvector in Percona docs&lt;/a>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18&lt;/a>&lt;/strong>: PostgreSQL with pgvector and a Docker image. Vanilla Postgres works too if you install the extension; I used Percona to try “their” Postgres + pgvector in a real deploy.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.python.org/" target="_blank" rel="noopener noreferrer">Python&lt;/a>&lt;/strong> + &lt;strong>&lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener noreferrer">FastAPI&lt;/a>&lt;/strong>: fast API setup, OpenAPI included, good libraries for crawl/embed/Postgres.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://huggingface.co/nomic-ai/nomic-embed-text-v1" target="_blank" rel="noopener noreferrer">nomic-embed-text-v1&lt;/a>&lt;/strong> + &lt;strong>&lt;a href="https://www.sbert.net/" target="_blank" rel="noopener noreferrer">sentence-transformers&lt;/a>&lt;/strong>: open model, 768 dims, CPU-friendly, no per-chunk API bill. Index and query must use the &lt;strong>same&lt;/strong> model; Nomic fits. I’ll compare others later.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo&lt;/a>&lt;/strong> + &lt;strong>JavaScript&lt;/strong>: thin widget on existing static site.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer">Docker&lt;/a>&lt;/strong> / &lt;strong>Docker Compose&lt;/strong>: same layout locally and on EC2.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://aws.amazon.com/ec2/" target="_blank" rel="noopener noreferrer">AWS EC2&lt;/a>&lt;/strong> + &lt;strong>nginx&lt;/strong>: HTTPS on &lt;code>search.percona.community&lt;/code>, CORS for GitHub Pages.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>AI-assisted development&lt;/strong> (I used &lt;a href="https://cursor.com/" target="_blank" rel="noopener noreferrer">Cursor&lt;/a>): the agent handled boilerplate, wiring, and Docker fixes. I reviewed everything. Any similar AI coding tool would work; the point is having one.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="how-long-it-took">How long it took&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>~6 hours&lt;/strong> with an AI coding assistant to a first prototype: crawl, API, Docker, basic demo;&lt;/li>
&lt;li>&lt;strong>~2 more days&lt;/strong> for schema changes, per-type ranking, embed/page widget, search history, dashboard, indexer fixes, EC2 deploy;&lt;/li>
&lt;li>&lt;strong>~$20&lt;/strong> in AI assistant tokens total.&lt;/li>
&lt;/ul>
&lt;p>Without AI I’d have stretched the same work over weeks. With the agent I mostly wrote tasks, checked output, and fixed edges.&lt;/p>
&lt;h3 id="about-the-code-and-repository">About the code and repository&lt;/h3>
&lt;p>I’m not publishing the repo yet. The code is tied to &lt;strong>percona.community&lt;/strong>: our RSS feeds, content types, Hugo widget, EC2 layout. It’s an internal prototype, not a reusable library.&lt;/p>
&lt;p>If you wanted a drop-in repo: porting someone else’s monolith often takes longer than rebuilding from a clear sketch. Part two will have architecture, schema, and stack notes enough for a Cursor agent (or similar) to rebuild for &lt;strong>your&lt;/strong> feeds and UI.&lt;/p>
&lt;p>Interested in a &lt;strong>generic open source&lt;/strong> or &lt;strong>search-as-a-service&lt;/strong> version? Say so in the comments; I’m weighing whether it’s worth a separate project.&lt;/p>
&lt;h3 id="whats-next">What’s Next&lt;/h3>
&lt;p>Try search on &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a> and comment what you find, especially where semantics beat the old substring search.&lt;/p>
&lt;p>Part &lt;strong>two&lt;/strong> will go inside: schema (including those three rewrites), chunking, HNSW, per-type result caps, and a local Docker Compose walkthrough.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>pgvector</category><category>search</category><category>embeddings</category><category>ai</category><media:thumbnail url="https://percona.community/blog/2026/05/search-part-1-cover_hu_be171443c370dc0c.jpg"/><media:content url="https://percona.community/blog/2026/05/search-part-1-cover_hu_3af6a3cd67b0be68.jpg" medium="image"/></item><item><title>Write for the Percona Community</title><link>https://percona.community/blog/2026/05/22/write-for-percona-community/</link><guid>https://percona.community/blog/2026/05/22/write-for-percona-community/</guid><pubDate>Fri, 22 May 2026 11:00:00 UTC</pubDate><description>You’ve fixed something gnarly in production this year. You’ve migrated a database that nobody wanted to touch. You’ve built something on top of Percona Operators, or Percona Toolkit, or Percona Monitoring and Management (PMM), and you’ve learned things along the way that aren’t written down anywhere yet.</description><content:encoded>&lt;p>You’ve fixed something gnarly in production this year. You’ve migrated a database that nobody wanted to touch. You’ve built something on top of Percona Operators, or Percona Toolkit, or Percona Monitoring and Management (PMM), and you’ve learned things along the way that aren’t written down anywhere yet.&lt;/p>
&lt;p>Write it up. We’ll publish it, and we’ll pay you.&lt;/p>
&lt;h2 id="what-were-doing">What we’re doing&lt;/h2>
&lt;p>The Percona Community Writers Program publishes technical posts from the people actually using these tools — DBAs, developers, contributors, and engineers running real workloads. Posts go up on &lt;a href="https://percona.community/blog" target="_blank" rel="noopener noreferrer">percona.community/blog&lt;/a> under your name, with your bio and links.&lt;/p>
&lt;p>For every post we publish, you get:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>$350&lt;/strong> paid out after publication&lt;/li>
&lt;li>&lt;strong>Community engagement points&lt;/strong> you can redeem in our swag store for t-shirts, stickers, and other items&lt;/li>
&lt;/ul>
&lt;p>The points stack across contributions. The more you write, the more you collect.&lt;/p>
&lt;h3 id="a-note-on-payment">A note on payment&lt;/h3>
&lt;p>&lt;em>Not everyone can accept payment for writing — employment contracts, tax situations, visa rules, and conflict-of-interest policies all get in the way. If that’s you, we’ll donate the same $350 to an open source project or community of your choice on your behalf. Tell us who to send it to when you pitch.&lt;/em>&lt;/p>
&lt;h2 id="what-we-want-to-read">What we want to read&lt;/h2>
&lt;p>Anything you’ve done with the Percona stack — or alongside it — that another engineer would learn from. Some directions to consider:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Percona Operators&lt;/strong> — running databases on Kubernetes, scaling decisions, upgrade paths, what surprised you&lt;/li>
&lt;li>&lt;strong>Percona Toolkit&lt;/strong> — how you use specific tools in your day-to-day, scripts you’ve built around them, edge cases&lt;/li>
&lt;li>&lt;strong>Migrations&lt;/strong> — moving between versions, between database engines, on-premises to cloud, the parts that aren’t in the docs&lt;/li>
&lt;li>&lt;strong>Troubleshooting&lt;/strong> — a real incident, what you saw, what fixed it, what you’d do differently&lt;/li>
&lt;li>&lt;strong>Percona Monitoring and Management (PMM)&lt;/strong> — dashboards you’ve built, alerts that actually catch things, integrations&lt;/li>
&lt;li>&lt;strong>Databases themselves&lt;/strong> — MySQL, PostgreSQL, MongoDB, MariaDB, Valkey, anything in the open source database world you’re hands-on with&lt;/li>
&lt;/ul>
&lt;p>We’re not only interested in Percona-product posts. If you’re active in the wider open source database community — contributing to MySQL, PostgreSQL, Valkey, or anywhere else — we want to hear about that work too. Your projects, your perspective, your hard-won opinions.&lt;/p>
&lt;h2 id="standards">Standards&lt;/h2>
&lt;p>Every submission is reviewed by the community team for technical accuracy and grammar before it goes live. We’re not gatekeeping — we’re making sure your name goes on something solid.&lt;/p>
&lt;p>One firm rule: &lt;strong>no AI-generated content&lt;/strong>. We run every submission through &lt;a href="https://gptzero.me/" target="_blank" rel="noopener noreferrer">GPTZero&lt;/a> and it has to come back clean. We’re publishing your voice and your experience, not a model’s summary of either. If you used AI to help draft, that’s fine — but the post needs to read as yours and pass the check.&lt;/p>
&lt;h2 id="how-to-start">How to start&lt;/h2>
&lt;p>Pitch us first. A couple of sentences on what you want to write about and why you’re the person to write it is enough. We’ll reply with feedback, a timeline, and any direction that helps you write a stronger post.&lt;/p>
&lt;p>You don’t need to be a published writer. You need to have done something and be willing to explain how. A 900-word post about how you debugged a replication lag issue last quarter is more valuable than a 3,000-word survey of the database landscape.&lt;/p>
&lt;p>Send pitches and questions to the Percona Community team — by filling in &lt;strong>&lt;a href="https://share.hsforms.com/2quoru-zrSli2l-89aiiJggg9e0" target="_blank" rel="noopener noreferrer">this form&lt;/a>&lt;/strong>.&lt;/p>
&lt;script src="https://js.hsforms.net/forms/embed/758664.js" defer>&lt;/script>
&lt;div class="hs-form-frame" data-region="na1" data-form-id="aaea2bbb-eceb-4a58-b697-ef3d6a288982" data-portal-id="758664">&lt;/div>
&lt;h3 id="open-topics-blog-talks-guides">Open topics: blog, talks, guides&lt;/h3>
&lt;p>Not sure where to start? Here are some directions we’d love to see covered. Pick one, narrow it down to something you’ve actually done, and pitch us.&lt;/p>
&lt;p>&lt;strong>Databases&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Automating database setup for production in under a few hours&lt;/li>
&lt;li>Backup and disaster recovery strategies that hold up&lt;/li>
&lt;li>Failure stories — what broke, what you learned&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>DevOps and reliability&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Database Reliability Engineering (DBRE) in practice&lt;/li>
&lt;li>Site Reliability Engineering (SRE) applied to databases&lt;/li>
&lt;li>Monitoring and SLAs that mean something&lt;/li>
&lt;li>Useful scripts you actually run in production&lt;/li>
&lt;li>Testing and QA for database changes&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Distributed computing&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Consensus algorithms and real-world implementations&lt;/li>
&lt;li>Synchronous vs asynchronous replication — trade-offs and where each fits&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>How-tos&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Moving from a single node to a cluster (any DB engine)&lt;/li>
&lt;li>Batch processing patterns&lt;/li>
&lt;li>Stream processing patterns&lt;/li>
&lt;li>Metrics that actually tell you something&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Open source&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Measuring your open source project’s success&lt;/li>
&lt;li>Bug squashing done right&lt;/li>
&lt;li>Licensing — what to know before you pick one&lt;/li>
&lt;li>Vendor lock-in and how to spot it early&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>We pay engineers to share what they’ve learned. That’s the whole offer. If you’ve got something worth writing, write it.&lt;/p>
&lt;h2 id="content-ownership-and-licensing">Content Ownership and Licensing&lt;/h2>
&lt;p>Contributors to the Percona Community Blog retain copyright of their work. By submitting content, authors grant Percona a non-exclusive, worldwide, royalty-free license to publish, distribute, and promote the content as part of the Percona Community platform. Unless otherwise specified, all community blog posts are published under the Creative Commons Attribution 4.0 International (CC BY 4.0) license.&lt;/p></content:encoded><author>Laura Czajkowski</author><category>Community</category><category>Percona</category><category>Open Source</category><category>MySQL</category><category>PostgreSQL</category><category>MongoDB</category><category>MariaDB</category><category>Valkey</category><media:thumbnail url="https://percona.community/blog/2026/05/laura_blog_cover_hu_224af4525ccba7ef.jpg"/><media:content url="https://percona.community/blog/2026/05/laura_blog_cover_hu_e77d31cc40c2e754.jpg" medium="image"/></item><item><title>Backrest's back, alright!</title><link>https://percona.community/blog/2026/05/19/backrests-back-alright/</link><guid>https://percona.community/blog/2026/05/19/backrests-back-alright/</guid><pubDate>Tue, 19 May 2026 11:00:00 UTC</pubDate><description>Events unfolded quickly over the course of a couple of weeks starting on 27 April 2026, when a message appeared on the pgBackRest project announcing: that the repository would be archived and active maintenance would stop.</description><content:encoded>&lt;p>Events unfolded quickly over the course of a couple of weeks starting on 27 April 2026, when a &lt;a href="https://pgbackrest.org/news.html" target="_blank" rel="noopener noreferrer">message appeared on the pgBackRest project announcing&lt;/a>:
that the repository would be archived and active maintenance would stop.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2068" height="1206" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_edf0219879dd12bf.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_3a780f434ca10a63.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_a93328b56bbefc92.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_a93328b56bbefc92.webp" alt="blog/2026/05/Jan-pgb-news-1.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For many in the PostgreSQL ecosystem, this landed like a shock. &lt;a href="https://pgbackrest.org/" target="_blank" rel="noopener noreferrer">pgBackRest&lt;/a> is one of the most widely used backup and recovery tools for PostgreSQL, deeply embedded in production environments across enterprises large and small. Now it was suddenly described as “&lt;a href="https://mydbanotebook.org/posts/pgbackrest-is-dead.-now-what/" target="_blank" rel="noopener noreferrer">dead&lt;/a>”, “&lt;a href="https://www.gabrielebartolini.it/articles/2026/04/why-the-cycle-of-open-source-sustainability-needs-to-be-virtuous/" target="_blank" rel="noopener noreferrer">EOL&lt;/a>”, or “&lt;a href="https://news.ycombinator.com/item?id=47919997" target="_blank" rel="noopener noreferrer">abandoned&lt;/a>”. The trigger was clear: its long-time maintainer, after more than a decade of work, announced he could no longer continue without sustainable funding and would archive the repository.
i
That message spread fast. The interpretation spread even faster.&lt;/p>
&lt;p>And it was wrong.&lt;/p>
&lt;h2 id="this-wasnt-eol">This wasn’t EOL&lt;/h2>
&lt;p>Open source software doesn’t simply “go end of life” in the way proprietary software does. There is no vendor switch flipped to OFF. No license revoked. No binaries disappearing overnight.&lt;/p>
&lt;p>What actually happens is more subtle and more important:&lt;/p>
&lt;ul>
&lt;li>Maintainers step away&lt;/li>
&lt;li>Funding runs out&lt;/li>
&lt;li>Work stops&lt;/li>
&lt;/ul>
&lt;p>That’s not EOL. That’s a sustainability gap.&lt;/p>
&lt;p>&lt;a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">pgBackRest&lt;/a> didn’t die. It hit a problem seen too often in open source world: a critical piece of infrastructure maintained by fewer and fewer people, until it ultimately depended on one person being able to justify working on it full time.&lt;/p>
&lt;h2 id="the-real-problem">The real problem&lt;/h2>
&lt;p>The message from the maintainer was not about abandoning the project. It was about reality:&lt;/p>
&lt;blockquote>
&lt;p>maintaining a widely used tool requires time, and time requires funding&lt;/p>&lt;/blockquote>
&lt;p>For years, pgBackRest was supported through corporate sponsorship from mainly one vendor. When that disappeared due to the Crunchy Data acquisition, so did the ability to keep investing the same level of effort.&lt;/p>
&lt;p>This is the “&lt;a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">Nebraska guy problem&lt;/a>” in action: software used by a large part of the industry, sustained by a very small number of people.&lt;/p>
&lt;p>Yes, anyone can fork the project (and some already did), but:&lt;/p>
&lt;ul>
&lt;li>trust doesn’t fork&lt;/li>
&lt;li>community doesn’t fork&lt;/li>
&lt;li>sustainability definitely doesn’t fork&lt;/li>
&lt;/ul>
&lt;p>A fork without coordination creates fragmentation without adding real value and that weakens the ecosystem. What pgBackRest needed was not a replacement, but continuity.&lt;/p>
&lt;h2 id="the-danger-of-bad-framing">The danger of bad framing&lt;/h2>
&lt;p>Calling the project “dead” shifted the conversation in the wrong direction.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_a95892beeb5ff40b.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_81f4927fa7c901e5.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_85bcece67808178.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_85bcece67808178.webp" alt="blog/2026/05/Jan-pgb-not-dead.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Instead of asking:&lt;/p>
&lt;blockquote>
&lt;p>how do we keep this project healthy?&lt;/p>&lt;/blockquote>
&lt;p>the discussion drifted at best toward:&lt;/p>
&lt;blockquote>
&lt;p>what is the strategic solution here?&lt;/p>&lt;/blockquote>
&lt;p>and more often to:&lt;/p>
&lt;blockquote>
&lt;p>what do we replace it with?&lt;/p>&lt;/blockquote>
&lt;p>and&lt;/p>
&lt;blockquote>
&lt;p>what do we name our fork?&lt;/p>&lt;/blockquote>
&lt;p>That’s a natural reaction, but it’s not a good one.&lt;/p>
&lt;p>Critical infrastructure should not be treated as disposable. Doing so erodes trust in the solutions we rely on and weakens the ecosystem. These foundational pieces should be treated as a shared responsibility so that the entire community becomes stronger.&lt;/p>
&lt;h2 id="what-happened-next">What happened next&lt;/h2>
&lt;p>Behind the scenes, things moved quickly, with coordination between David and companies active in the PostgreSQL community.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2134" height="1084" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_3e33849c387f5aca.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_671934f762dce154.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_f4016fa9b115ee82.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_f4016fa9b115ee82.webp" alt="blog/2026/05/Jan-pgb-news-2.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Conversations started across companies, contributors and the wider ecosystem. The goal wasn’t to “rescue” pgBackRest, but to do something far more valuable: to restore a sustainable model around it.&lt;/p>
&lt;p>This is what open source actually requires: not heroics, but coordination.&lt;/p>
&lt;h2 id="so-whats-with-pgbackrest">So what’s with pgBackRest?&lt;/h2>
&lt;p>It’s all good. Well, better.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_a9e05645c0920c40.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_e56fac2896f377f2.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_491d36ba3bcded32.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_491d36ba3bcded32.webp" alt="blog/2026/05/Jan-pgb-back-cover.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The short version:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://pgbackrest.org/news.html#will-continue" target="_blank" rel="noopener noreferrer">Multiple companies coordinated together&lt;/a> to &lt;a href="https://www.globenewswire.com/news-release/2026/05/19/3297383/0/en/open-source-stays-open-percona-sponsors-pgbackrest-to-keep-postgresql-backups-running.html" target="_blank" rel="noopener noreferrer">ensure continued funding and support around pgBackRest&lt;/a>&lt;/li>
&lt;li>Engineering effort is now being shared more broadly to expand the contributor and maintainer base&lt;/li>
&lt;li>Discussions around longer term sustainability and governance in the PostgreSQL ecosystem accelerated significantly&lt;/li>
&lt;li>&lt;strong>Percona&lt;/strong> played an active role in coordinating these efforts, contributing engineering resources, and helping bring organizations together around a sustainable path forward&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="2134" height="1914" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_33eb9ebd33d88005.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_a16d70a524b6df90.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_450f770f15b12e4f.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_450f770f15b12e4f.webp" alt="blog/2026/05/Jan-pgb-news-3.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The project was never closed.&lt;/p>
&lt;h2 id="the-way-forward-is-open">The way (forward) is open&lt;/h2>
&lt;p>pgBackRest’s situation is not unique. It’s a signal.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/Jan-pgb-back_hu_edeaf7070a62a379.webp 768w, https://percona.community/blog/2026/05/Jan-pgb-back_hu_b7d32bd1f8c967d0.webp 480w, https://percona.community/blog/2026/05/Jan-pgb-back_hu_b092c398056379ed.webp 1400w" src="https://percona.community/blog/2026/05/Jan-pgb-back_hu_b092c398056379ed.webp" alt="blog/2026/05/Jan-pgb-back.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The PostgreSQL ecosystem depends on a wide range of tools that don’t have the same visibility, or funding, as the database itself. That gap is becoming harder to ignore.&lt;/p>
&lt;p>There’s growing alignment on a few things:&lt;/p>
&lt;ul>
&lt;li>sustainability needs to be intentional&lt;/li>
&lt;li>funding needs to be easier to organize&lt;/li>
&lt;li>engineering effort needs to be shared&lt;/li>
&lt;/ul>
&lt;p>Whether that leads to an umbrella foundation or another model, one thing is clear: the ecosystem needs structures that support both users and maintainers.&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pgBackRest</category><media:thumbnail url="https://percona.community/blog/2026/05/Jan-pgb-back-alright_hu_376537496a0e665b.jpg"/><media:content url="https://percona.community/blog/2026/05/Jan-pgb-back-alright_hu_a681e769ea615ca5.jpg" medium="image"/></item><item><title>Two projects, one mission - hackorum and pginbox join forces</title><link>https://percona.community/blog/2026/05/13/two-projects-one-mission-hackorum-and-pginbox-join-forces/</link><guid>https://percona.community/blog/2026/05/13/two-projects-one-mission-hackorum-and-pginbox-join-forces/</guid><pubDate>Wed, 13 May 2026 10:00:00 UTC</pubDate><description>Last week, Zsolt and I jumped on a call with someone who had been building something remarkably similar to what we had been working on, completely independently. That someone is Jack Bonatakis, the creator of pginbox.dev, and that call turned into one of the most energizing conversations we’ve had since launching hackorum.dev.</description><content:encoded>&lt;p>Last week, Zsolt and I jumped on a call with someone who had been building something remarkably similar to what we had been working on, completely independently. That someone is Jack Bonatakis, the creator of &lt;a href="https://pginbox.dev/" target="_blank" rel="noopener noreferrer">pginbox.dev&lt;/a>, and that call turned into one of the most energizing conversations we’ve had since launching &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">hackorum.dev&lt;/a>.&lt;/p>
&lt;h3 id="two-builders-one-problem">&lt;strong>Two builders, one problem&lt;/strong>&lt;/h3>
&lt;p>When we launched Hackorum back in January, the goal was simple but important: make the pg-hackers mailing list actually readable. The list is the heartbeat of PostgreSQL core development, patches are proposed, debated, iterated on, and committed entirely through it. But the interface? Decades-old email threads. Dense, fast-moving, and not exactly welcoming to newcomers or even experienced contributors trying to manage the volume.&lt;/p>
&lt;p>We built Hackorum to change that, a forum-style, read-only web view with commitfest integration, contributor profiles, read tracking, shared team notes, and more. Something that respects the existing email-based workflow while dramatically lowering the barrier to follow along.&lt;/p>
&lt;p>Jack didn’t know at the time was that hackorum.dev existed but he reached the exact same conclusion, independently, and had already started building his own answer: pginbox.dev.&lt;/p>
&lt;h3 id="who-is-jack">&lt;strong>Who is Jack?&lt;/strong>&lt;/h3>
&lt;p>&lt;a href="https://www.linkedin.com/in/jack-bonatakis/" target="_blank" rel="noopener noreferrer">Jack Bonatakis&lt;/a> is a Principal Software Engineer focused on Data &amp; Insights, based in Washington, D.C and currently working at Robin, a Boston-based startup. His professional background spans data engineering, cloud analytics, and building data-driven platforms, with stints at MITRE working on government-sector data solutions, as well as roles at Rhino Insurance and DAS42, a cloud analytics consultancy. He holds a Master’s degree from the University of Colorado Boulder and has built a career on turning complex, noisy data into something people can actually use.&lt;/p>
&lt;p>Which makes it completely unsurprising that he looked at the pg-hackers mailing list, one of the most information-dense, high-volume technical discussions on the internet, and thought: &lt;em>this could be so much better.&lt;/em>&lt;/p>
&lt;p>That instinct led him to build pginbox.dev: his own take on giving PostgreSQL’s mailing list ecosystem a more modern, inbox-style interface. He built it almost immediately after we published our first blog post about Hackorum. We were first, but only just. And the fact that two completely separate teams arrived at the same idea, at nearly the same time, with similar approaches, says something important about how real and widely felt this problem is.&lt;/p>
&lt;h3 id="joining-forces">&lt;strong>Joining forces&lt;/strong>&lt;/h3>
&lt;p>When we got on the call with Jack, there was no awkwardness about the overlap. Quite the opposite, we spent most of the time excitedly comparing notes on what we’d each built, what worked, what didn’t, and where we wanted to take things. The conversation made it obvious pretty quickly that running two separate tools for the same community didn’t serve anyone well.&lt;/p>
&lt;p>So we made the call together: pginbox.dev will be sunset, and Jack will be joining the Hackorum project going forward. One project, a stronger team, and a clearer path for the PostgreSQL community.&lt;/p>
&lt;p>This is exactly what open source is supposed to feel like. Not a race, not a territory dispute, people who cared enough to build something from scratch, discovering they’d be stronger together than apart, and choosing collaboration over competition without a second thought. We’re not building Hackorum for any single company or any individual’s portfolio. We’re building something that should last, something genuinely useful to the PostgreSQL community for years to come. A more inclusive, more navigable hacker future, built together.&lt;/p>
&lt;p>Welcome to the team, Jack. We’re genuinely glad you built what you built. And we’re even more glad you’re building the next chapter with us working on hackorum.dev&lt;/p>
&lt;p>If you haven’t tried Hackorum yet, visit &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">hackorum.dev&lt;/a>. And if you want to get involved, here’s how:&lt;/p>
&lt;ul>
&lt;li>💬 &lt;strong>Join the conversation&lt;/strong> and hop into our &lt;a href="https://discordapp.com/channels/1258108670710124574/1471524461374083186" target="_blank" rel="noopener noreferrer">Hackorum Discord channel&lt;/a> to ask questions, share ideas, or just follow along as we build.&lt;/li>
&lt;li>🛠️ &lt;strong>Explore the code,&lt;/strong> everything is open source and available on &lt;a href="https://github.com/hackorum-dev/hackorum" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.&lt;/li>
&lt;li>🐛 &lt;strong>Report a bug or request a feature,&lt;/strong> we track everything in the our &lt;a href="https://github.com/hackorum-dev/hackorum/issues" target="_blank" rel="noopener noreferrer">GitHub Issues&lt;/a>.&lt;/li>
&lt;/ul></content:encoded><author>Kai Wagner</author><category>Percona</category><category>PostgreSQL</category><category>Community</category><category>pg_kwagner</category><media:thumbnail url="https://percona.community/blog/2026/05/hackorum-pginbox_hu_6cad50a0c410f39e.jpg"/><media:content url="https://percona.community/blog/2026/05/hackorum-pginbox_hu_3ce14bf5382a40de.jpg" medium="image"/></item><item><title>Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and Surviving on Chips!</title><link>https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/</link><guid>https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/</guid><pubDate>Mon, 11 May 2026 00:00:00 UTC</pubDate><description>On May 7th, Keith (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to MongoDB.local London! The event was amazing and packed with insights about where the database ecosystem is heading.</description><content:encoded>&lt;p>On May 7th, &lt;strong>Keith&lt;/strong> (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to &lt;a href="https://www.mongodb.com/events/mongodb-local/london" target="_blank" rel="noopener noreferrer">MongoDB.local London&lt;/a>! The event was amazing and packed with insights about where the database ecosystem is heading.&lt;/p>
&lt;p>If there was one massive takeaway from the day, it was this: &lt;strong>We are officially in the Era of AI and “Agentic” Systems.&lt;/strong> During the event, the message was clear: we are shifting from basic LLMs (that just answer a prompt and forget it) to autonomous AI Agents that follow a continuous loop of Perception → Planning → Action. MongoDB’s President and CEO, CJ Desai, repeated a powerful phrase:&lt;/p>
&lt;blockquote>
&lt;p>While AI models change rapidly, the Data Layer is the constant.&lt;/p>&lt;/blockquote>
&lt;p>
&lt;figure>&lt;img width="1376" height="893" sizes="(max-width: 1376px) 100vw, 1376px" srcset="https://percona.community/blog/2026/05/mongodb-desai_hu_d05b976cb7926744.webp 768w, https://percona.community/blog/2026/05/mongodb-desai_hu_8a807e78d7585eac.webp 480w, https://percona.community/blog/2026/05/mongodb-desai_hu_912d92aa6b06692d.webp 1376w" src="https://percona.community/blog/2026/05/mongodb-desai_hu_912d92aa6b06692d.webp" alt="ceo" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Here is a look at our day, what we learned, and the fun we had along the way!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1376" height="1002" sizes="(max-width: 1376px) 100vw, 1376px" srcset="https://percona.community/blog/2026/05/mongodb-team_hu_e04ff3d8d0088bf1.webp 768w, https://percona.community/blog/2026/05/mongodb-team_hu_7b0573cdee89e4f3.webp 480w, https://percona.community/blog/2026/05/mongodb-team_hu_4eee40ce0e67bf17.webp 1376w" src="https://percona.community/blog/2026/05/mongodb-team_hu_4eee40ce0e67bf17.webp" alt="team" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="arriving-early-and-chasing-badges">Arriving Early and Chasing Badges&lt;/h3>
&lt;p>We got a great tip before the event: arrive early to get a head start on the gamified learning! MongoDB had a super nice setup where you could take tests on Credly to earn knowledge badges.&lt;/p>
&lt;p>We jumped right in. I got a &lt;strong>MongoDB Overview badge&lt;/strong>, but Keith was on a mission. He completed three different tests (including MongoDB for Developers) and unlocked some cool swag: a really cute, high-quality bag! It was a brilliant way to get attendees engaged right from the morning.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1018" height="724" sizes="(max-width: 1018px) 100vw, 1018px" srcset="https://percona.community/blog/2026/05/mongodb-skills_hu_858439623f46f5d3.webp 768w, https://percona.community/blog/2026/05/mongodb-skills_hu_584dc48d12918d84.webp 480w, https://percona.community/blog/2026/05/mongodb-skills_hu_dfaf421f5927c84d.webp 1018w" src="https://percona.community/blog/2026/05/mongodb-skills_hu_dfaf421f5927c84d.webp" alt="skills" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://www.credly.com/organizations/mongodb/collections/mongodb-skill-badges/badge_templates" target="_blank" rel="noopener noreferrer">Here&lt;/a> are more badges in case you want to get yours!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1268" height="961" sizes="(max-width: 1268px) 100vw, 1268px" srcset="https://percona.community/blog/2026/05/mongodb-badges_hu_59936d164f1b38bd.webp 768w, https://percona.community/blog/2026/05/mongodb-badges_hu_865ba0f28704bacd.webp 480w, https://percona.community/blog/2026/05/mongodb-badges_hu_d3fd3baeff71149b.webp 1268w" src="https://percona.community/blog/2026/05/mongodb-badges_hu_d3fd3baeff71149b.webp" alt="badges" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="general-session-highlights">General Session Highlights&lt;/h3>
&lt;p>We spent a lot of our time in the main room for the General Session, and the announcements were packed with impressive numbers and tech:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MongoDB 8.3 is Fast:&lt;/strong> Osmar Olivo (Senior Director, Database Product Management) shared that the new version brings up to 35% more write throughput, 45% more read throughput, and 15% more for ACID transactions.&lt;/li>
&lt;li>&lt;strong>The Scale is Real:&lt;/strong> We learned that Stripe uses MongoDB to process over $1 trillion in payments volume every year (maintaining 5 nines of availability!). Osmar framed this perfectly: that is 1.5% of the global GDP running through MongoDB.&lt;/li>
&lt;li>&lt;strong>LangGraph.js Store Integration:&lt;/strong> This was a big one for developers. MongoDB is positioning itself as the “memory hard drive” for AI agents. By supporting JavaScript and TypeScript, they are making it super easy for companies to use their existing web developers to build complex AI workflows.&lt;/li>
&lt;li>&lt;strong>Hugging Face Partnership:&lt;/strong> They are scaling with MongoDB Atlas to support over 3 million models, officially tying themselves to the “GitHub for AI.”&lt;/li>
&lt;/ul>
&lt;p>Feel free to explore the recorded sessions for more: &lt;a href="https://www.youtube.com/watch?v=mHOQWeuoreM&amp;t=1877s" target="_blank" rel="noopener noreferrer">MongoDB.local London 2026&lt;/a>&lt;/p>
&lt;h3 id="guest-speakers">Guest Speakers&lt;/h3>
&lt;p>&lt;strong>Ulku Rowe&lt;/strong> (CIO, Commercial Business at Lloyds Banking Group) talked about this being the “Decade of AI.” Lloyds is actively upskilling their current engineers through an internal “AI Academy” built in partnership with Cambridge University! She emphasized that as they build out this infrastructure, partnerships are absolutely critical to their success.&lt;/p>
&lt;p>We also heard from &lt;strong>Alex Holt&lt;/strong> from ElevenLabs, a company focused on producing the absolute best, human-sounding voice AI. Their scale is wild: they have 40 million agents running and hit $500 million in Annual Recurring Revenue in just 3 years! Alex mentioned that because many enterprises don’t know how to build agents yet, ElevenLabs uses “forward deployed engineers” to sit directly with customers to build, deploy, and prove the ROI of their voice agents.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1366" height="857" sizes="(max-width: 1366px) 100vw, 1366px" srcset="https://percona.community/blog/2026/05/mongodb-eleven_hu_ed2faba7cd1b5468.webp 768w, https://percona.community/blog/2026/05/mongodb-eleven_hu_e28510a3ff2100ba.webp 480w, https://percona.community/blog/2026/05/mongodb-eleven_hu_56f02d97a48c8a54.webp 1366w" src="https://percona.community/blog/2026/05/mongodb-eleven_hu_56f02d97a48c8a54.webp" alt="eleven" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="the-hands-on-workshop-and-our-lunch-diet">The Hands-on Workshop and Our Lunch “Diet”&lt;/h3>
&lt;p>Later in the day, we attended a hands-on workshop: &lt;strong>Designing Memory Systems for AI Agents&lt;/strong>, hands-on workshop about how AI agents can remember information and use it later to give better responses. We used Python and MongoDB Atlas to build memory into an AI agent and learned how to store, search, update, and manage that memory.
The setup was good, everything was prepared in advance so we could focus on executing the commands and truly understanding the concepts. At the end, we answered some questions and earned another badge!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1264" height="750" sizes="(max-width: 1264px) 100vw, 1264px" srcset="https://percona.community/blog/2026/05/mongodb-workshop_hu_1d4bb8c191dcf454.webp 768w, https://percona.community/blog/2026/05/mongodb-workshop_hu_a1bd3d4eab2fc275.webp 480w, https://percona.community/blog/2026/05/mongodb-workshop_hu_73b6877a1ca1c922.webp 1264w" src="https://percona.community/blog/2026/05/mongodb-workshop_hu_73b6877a1ca1c922.webp" alt="badges" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>However, the workshop ran until 1:00 PM. One of our friends had warned us to “go for food fast,” but we were too focused on the workshop! By the time we made it to the lunch area, all the main food was completely sold out.&lt;/p>
&lt;p>&lt;strong>How did we survive?&lt;/strong> Chips, candies, and a lot of beverages. Between the sodas, coffee, and tea, we kept our energy, but it was definitely a funny learning experience for next time! I can imagine Keith arriving home for dinner!!&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2026/05/mongodb-gif.gif" alt="badges" />&lt;/figure>&lt;/p>
&lt;h3 id="exploring-the-sponsor-hall-and-doing-a-podcast">Exploring the Sponsor Hall (And Doing a Podcast!)&lt;/h3>
&lt;p>We spent our afternoon speaking with sponsors and even got to participate in a quick podcast focusing on AI and how Atlas is being used as a strong platform for these projects!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1176" height="791" sizes="(max-width: 1176px) 100vw, 1176px" srcset="https://percona.community/blog/2026/05/mongodb-podcast_hu_814443417fd25cc6.webp 768w, https://percona.community/blog/2026/05/mongodb-podcast_hu_9a53e51a3387a18e.webp 480w, https://percona.community/blog/2026/05/mongodb-podcast_hu_34afb51909661144.webp 1176w" src="https://percona.community/blog/2026/05/mongodb-podcast_hu_34afb51909661144.webp" alt="podcast" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The person being interviewed was &lt;strong>Bikram Das&lt;/strong>, who is Chief Data Architect at Tata Consulting Services, and we had a great chat with him. TCS and MongoDB have partnered on a super impressive real-time payment and fraud detection platform. They use autonomous AI agents to instantly assess risk, investigate anomalies, and route safe transactions to networks like Visa and SWIFT without any downtime.&lt;/p>
&lt;p>We also talked with IBM folks; they showed us their “plug-and-play” enterprise AI foundation. They are focused on letting large companies safely deploy AI agents without having to completely rip out and rebuild their current data infrastructure.&lt;/p>
&lt;p>We also stopped by the Accenture booth! They are actively working on integrating AI directly into their platforms so they can offer smarter, more advanced solutions to their customers.&lt;/p>
&lt;h3 id="wrapping-up">Wrapping Up!&lt;/h3>
&lt;p>To cap off a great day, MongoDB had one last treat. If you took less than 3 minutes to fill out the end-of-event survey, they handed you a super nice pair of socks. &lt;em>(We love community ideas like this!)&lt;/em>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="534" height="651" sizes="(max-width: 534px) 100vw, 534px" srcset="https://percona.community/blog/2026/05/mongodb-socks_hu_cf541f5cd321e494.webp 480w, https://percona.community/blog/2026/05/mongodb-socks_hu_de3306139b649507.webp 534w" src="https://percona.community/blog/2026/05/mongodb-socks_hu_de3306139b649507.webp" alt="podcast" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Overall, &lt;strong>MongoDB.local London&lt;/strong> was a great experience. It was a nice space to learn, connect, have hands-on experience, and see exactly how the database world is evolving to meet the Agentic AI era head-on.&lt;/p>
&lt;p>See you at the next event!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="699" height="614" sizes="(max-width: 699px) 100vw, 699px" srcset="https://percona.community/blog/2026/05/mongodb-percona_hu_f7f79191fbdbaa48.webp 480w, https://percona.community/blog/2026/05/mongodb-percona_hu_e0b941a7bf1554f2.webp 699w" src="https://percona.community/blog/2026/05/mongodb-percona_hu_e0b941a7bf1554f2.webp" alt="bye" loading="lazy" />&lt;/figure>&lt;/p></content:encoded><author>Edith Puclla</author><author>Keith Quinn</author><category>AI</category><category>events</category><category>MongoDB</category><category>database</category><media:thumbnail url="https://percona.community/blog/2026/05/mongodb-team_hu_881eec7d3d8a0ed7.jpg"/><media:content url="https://percona.community/blog/2026/05/mongodb-team_hu_5d1924c56835b879.jpg" medium="image"/></item><item><title>Meet the Percona Community team</title><link>https://percona.community/blog/2026/05/07/meet-the-percona-community-team/</link><guid>https://percona.community/blog/2026/05/07/meet-the-percona-community-team/</guid><pubDate>Thu, 07 May 2026 09:00:00 UTC</pubDate><description>We’ve just landed on X and Mastodon, and before the first real post goes out, we wanted to do something we don’t do often enough: introduce ourselves.</description><content:encoded>&lt;p>We’ve just landed on X and Mastodon, and before the first real post goes out, we wanted to do something we don’t do often enough: introduce ourselves.&lt;/p>
&lt;p>If you’ve been to Percona Live, a Percona.connect, a PGConf, KubeCon, FOSDEM, or pretty much any open source database event in the past few years, you’ve probably already met one of us. We’re the people behind the booth, on stage, organising the speakers, herding the giant Jenga set, or trying to convince you to play a quick game of chess between sessions. Now we’re also the people behind @PerconaCommunity on X and our new Mastodon account on the fediverse.&lt;/p>
&lt;p>Each of us will sign our posts with our initials, so you’ll always know who you’re talking to. Here’s who we are.&lt;/p>
&lt;h2 id="laura-czajkowski---director-of-community-lc">Laura Czajkowski - Director of Community (LC)&lt;/h2>
&lt;p>Laura runs the team. She’s been in open source community work since the early 2000s, starting at the University of Limerick’s Skynet computer society and going on to lead community at Canonical (Ubuntu), MongoDB, Couchbase, Vonage, Solace, and Dragonfly before joining Percona. Former Ubuntu LoCo Council and Community Council member. Outside work she’s a Munster and Ireland rugby fan, runs a book club, plays tennis, and books regular trips to Disney World. Find her at &lt;a href="https://laura.community" target="_blank" rel="noopener noreferrer">laura.community&lt;/a>.&lt;/p>
&lt;h2 id="alastair-turner---postgres-community-advocate-at">Alastair Turner - Postgres Community Advocate (AT)&lt;/h2>
&lt;p>Alastair has been working with databases since 1995, settling on Postgres around 2002. If you’ve spoken to anyone at Percona about PostgreSQL, Kubernetes, Transparent Data Encryption, or extensions, there’s a good chance it was him. He writes regularly on the &lt;a href="https://percona.community/" target="_blank" rel="noopener noreferrer">Percona Community blog&lt;/a> and speaks at PGConf events across Europe and North America. He’s particularly interested in how open source communities work together - and what they can learn from each other.&lt;/p>
&lt;h2 id="daniil-bazhenov---senior-community-manager-db">Daniil Bazhenov - Senior Community Manager (DB)&lt;/h2>
&lt;p>Daniil organises our conference speakers, runs the Percona Forums, and has been a long-time contributor to the Percona Community blog. If you’ve ever submitted a talk to Percona Live or asked a question on forums.percona.com, you’ve crossed paths with him. He writes hands-on technical content too - GitOps with ArgoCD, PMM monitoring, Percona Everest from source - and hosts the Russian-language Percona Podcast.&lt;/p>
&lt;h2 id="kyle-flanagan---global-manager-events-kf">Kyle Flanagan - Global Manager, Events (KF)&lt;/h2>
&lt;p>Kyle is the reason any of our events actually happen. He runs Percona’s global events programme, from Percona Live and Percona.connect to our presence at Open Source Summit, KubeCon, and dozens of regional events each year. Before Percona, he ran executive events at Utah Valley University. If you’ve grabbed a sticker at one of our booths, Kyle probably packed the box it came in.&lt;/p>
&lt;h2 id="edith-puclla---technology-evangelist-ep">Edith Puclla - Technology Evangelist (EP)&lt;/h2>
&lt;p>Originally from Peru, now based in London, Edith is a CNCF Ambassador, Docker Captain, and Data on Kubernetes Ambassador. Her background is in DevOps and infrastructure - Kubernetes, GPUs, Linux, distributed systems - and she contributes to translating Kubernetes documentation into Spanish through SIG-Operators. She’s a regular speaker at FOSDEM, KubeCon, Cloud Native Rejekts, and Percona University events across Latin America.&lt;/p>
&lt;h2 id="why-were-doing-this">Why we’re doing this&lt;/h2>
&lt;p>We spend a lot of our time at events because that’s where the most useful conversations happen - the ones over coffee, at the booth, in the hallway between talks. Being on social gives us a way to keep those conversations going when we’re not in the same room. Expect event updates, contributor shout-outs, things we’ve found useful, and the occasional opinion. If we’ve shared it, we’ve actually read it.&lt;/p>
&lt;p>Photo below was taken at our recent team offsite in Antalya - five people who genuinely like working together, in case the smiles don’t give it away.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1974" height="1249" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/05/community-team-with-names_hu_8c479bfe5cae5210.webp 768w, https://percona.community/blog/2026/05/community-team-with-names_hu_d47023a08f522620.webp 480w, https://percona.community/blog/2026/05/community-team-with-names_hu_10e256588ef14484.webp 1400w" src="https://percona.community/blog/2026/05/community-team-with-names_hu_10e256588ef14484.webp" alt="The Percona Community team in Antalya" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Find us:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>X: &lt;a href="https://x.com/PerconaBytes" target="_blank" rel="noopener noreferrer">@PerconaBytes&lt;/a>&lt;/li>
&lt;li>Mastodon: &lt;a href="https://mastodon.social/@PerconaBytes" target="_blank" rel="noopener noreferrer">@PerconaBytes&lt;/a>&lt;/li>
&lt;li>Forums: &lt;a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">forums.percona.com&lt;/a>&lt;/li>
&lt;li>Community blog: &lt;a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Come say hi. If we’re at an event near you, the booth is open - and so is the giant Jenga.&lt;/p></content:encoded><author>Laura Czajkowski</author><category>Community</category><category>Percona</category><category>Open Source</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2026/05/community-team_hu_56354b0194549b71.jpg"/><media:content url="https://percona.community/blog/2026/05/community-team_hu_caeaa79bd299186a.jpg" medium="image"/></item><item><title>How I Stopped Babysitting My Coding Agent (With Dotfiles)</title><link>https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/</link><guid>https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/</guid><pubDate>Tue, 05 May 2026 00:00:00 UTC</pubDate><description>Most developers at least try to use coding agents for development-related tasks, but babysitting LLMs and managing their permissions is no fun. Completely skipping permission checks is a dangerous idea on your main machine, and setting up containers or VMs for sandboxing is a pain. Can we do better?</description><content:encoded>&lt;p>Most developers at least try to use coding agents for development-related tasks, but babysitting LLMs and managing their permissions is no fun.
Completely skipping permission checks is a dangerous idea on your main machine, and setting up containers or VMs for sandboxing is a pain.
Can we do better?&lt;/p>
&lt;h3 id="the-autonomy-problem">The autonomy problem&lt;/h3>
&lt;p>If you work in software development, you have most certainly heard the phrase:&lt;/p>
&lt;blockquote>
&lt;p>Let’s just use an LLM to solve it!&lt;/p>&lt;/blockquote>
&lt;p>People tend to forget that it’s a bit more complicated than this:
anybody can easily use LLMs, of course, but using them properly is a different question.
Ideally, we could all just download a simple tool, give it some instructions, and relax:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="853" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2026/05/ai-gardening_hu_33a54960704ad98e.webp 768w, https://percona.community/blog/2026/05/ai-gardening_hu_147bf91b4a2c8ef3.webp 480w, https://percona.community/blog/2026/05/ai-gardening_hu_42a05c1f0289c8d8.webp 1280w" src="https://percona.community/blog/2026/05/ai-gardening_hu_42a05c1f0289c8d8.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;div class="admonition admonition--warning">
&lt;p>Disclaimer: your employer might not approve if you do gardening during work hours; I suggest choosing a different activity in this case!&lt;/p>
&lt;/div>
&lt;p>In all seriousness, every panel in the above image contains details that people tend to ignore, which either results in inefficient workflows or the creation of slop.&lt;/p>
&lt;p>We can’t talk about all of them in one go; it would be overly long and complex.
I’ll only focus on panel 2:
what can we do to ensure our uninterrupted &lt;del>gardening&lt;/del> normal work?&lt;/p>
&lt;p>If you simply download Claude/Codex and start using the CLI tool, VS Code extension, or anything else, you’ll quickly get bored of all the babysitting.&lt;/p>
&lt;blockquote>
&lt;p>Hey, user, can I execute another slightly different &lt;code>ls&lt;/code> command?&lt;/p>&lt;/blockquote>
&lt;p>Either you decide it isn’t worth the effort because of all the interruptions, or you start blindly hitting Enter: “of course I approve, it should be safe…”&lt;/p>
&lt;ol>
&lt;li>Are you really thoroughly reviewing every command it throws at you?&lt;/li>
&lt;li>Even that 100-line bash script the TUI doesn’t display properly, because it wouldn’t fit on the screen?&lt;/li>
&lt;li>Have you ever seen an agent circumvent directory permissions by accessing the restricted files through a one-off script instead?&lt;/li>
&lt;/ol>
&lt;p>Fine-grained permissions of course exist, and in theory, you could try to configure something like that.
But let’s be honest, most of us won’t take the time, and we likely won’t notice if (3) happens as part of a long script.&lt;/p>
&lt;h3 id="let-the-ai-run-free">Let the AI run free&lt;/h3>
&lt;p>That’s the point where you might discover the other option:
completely disabling the permission system and letting the AI do whatever it wants.&lt;/p>
&lt;p>Nothing can go wrong, it’s only on your machine, right?&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="786" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2026/05/ai-running_hu_38c01365aa700235.webp 768w, https://percona.community/blog/2026/05/ai-running_hu_a7d1e3905786af20.webp 480w, https://percona.community/blog/2026/05/ai-running_hu_bf25d1ba233085f2.webp 1280w" src="https://percona.community/blog/2026/05/ai-running_hu_bf25d1ba233085f2.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Except that:&lt;/p>
&lt;ul>
&lt;li>it will also have full network access, both for reading and posting&lt;/li>
&lt;li>it can read all your secrets: its own OAuth token, your SSH key, and so on…&lt;/li>
&lt;li>do you load your SSH key into ssh-agent? That’s convenient so you don’t have to enter your password every time, but do you also have a hardware key you have to touch on every use, or can the AI force-push your repository and later say&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>You are absolutely right! I shouldn’t have done that. If you have backups you can restore them with the following steps: …&lt;/p>&lt;/blockquote>
&lt;p>Or it might end up in any number of similar situations.
Coding agents aren’t malicious by design, but they can be subject to prompt injection from the web, or simply reach dumb conclusions.
There’s a good reason why Claude, for example, calls this option &lt;code>--dangerously-skip-permissions&lt;/code>.&lt;/p>
&lt;h3 id="put-them-in-a-cage">Put them in a cage!&lt;/h3>
&lt;p>The next obvious choice is to let them run free, but only within a cell:
run the agent inside a container or virtual machine, where it can only access what you let it.&lt;/p>
&lt;p>This, however, costs us some convenience, as we face new issues:&lt;/p>
&lt;ul>
&lt;li>If we completely separate the environment, we can’t access it from our main system.
Allowing AI tools to push to your repo without confirmation is a bad idea, but maybe you yourself should be able to push somehow?
Or to verify the changes in a more complex, outside environment?&lt;/li>
&lt;li>Our environment and the AI’s environment are different… which means we have to set up both.
I hope your project is easy to bootstrap, with proper scripting so you don’t have to do this by hand.
But is your development environment also easy to bootstrap?&lt;/li>
&lt;/ul>
&lt;p>There are some existing, ready-to-use solutions: for example, both Claude Code and OpenAI Codex have support for &lt;a href="https://containers.dev/" target="_blank" rel="noopener noreferrer">devcontainers&lt;/a>.
If you want an easy setup, these can be an option.&lt;/p>
&lt;p>However, I wanted more:
to replicate my main setup exactly – the same compilers, tools, shell and editor settings, and so on.
The AI tools should have the same executables available.
If I have to edit or do something directly in the container, I shouldn’t be surprised by something working differently.&lt;/p>
&lt;p>That’s when I remembered: I already have a &lt;a href="https://github.com/dutow/dotfiles" target="_blank" rel="noopener noreferrer">dotfiles&lt;/a> repo. Can I make it even better for this use case?&lt;/p>
&lt;h3 id="automate-all-the-things">Automate all the things!&lt;/h3>
&lt;p>The idea of dotfiles is simple:
a repository where you store your configuration, so when you reinstall your system, or when you have to start using another one, you can quickly replicate your preferred settings.
Editors, shells, git – everything works the same, without spending hours figuring it all out again.&lt;/p>
&lt;p>The problem is that it usually only focuses on configuring an already properly installed system.
When you only buy a new PC every few years, or system administrators already set up every server you have to use before your first login, this isn’t a big issue.&lt;/p>
&lt;p>But when you want to be able to quickly set up and iterate with throwaway systems?
Then you need better automation!&lt;/p>
&lt;p>This is also a solved problem; tools like Ansible and Puppet exist.&lt;/p>
&lt;p>The idea is simple:&lt;/p>
&lt;ul>
&lt;li>instead of manually setting up your system, use an automation tool to install and configure everything&lt;/li>
&lt;li>you can leverage free CI services to make sure that your scripts work when run on a clean system&lt;/li>
&lt;li>while docker/podman traditionally uses its own setup scripting, it is possible to build an image using the same automation tool instead&lt;/li>
&lt;li>the result? Main PC, containers, virtual machines, and quick VPS instances all behaving exactly the same way!&lt;/li>
&lt;/ul>
&lt;p>The downside is, of course, that you either have to reinstall your main PC once your new setup is good enough, or accept that it will be slightly different until you do so.
I went with the reinstall; it’s easy once you have things working.&lt;/p>
&lt;p>And if you don’t know any of these tools?
That’s the best part – we’re using AI, and AI knows them well.&lt;/p>
&lt;h3 id="a-side-note-on-architecture">A side note on architecture&lt;/h3>
&lt;p>The focus of this blog post is panel 2, not the others.
But I want to at least mention that the architecture and human review, including design review, are as important as with any other AI-driven software project.&lt;/p>
&lt;p>If you completely vibe-code it and create an unmaintainable, sloppy dotfiles configuration, you are going to regret it later. This is your everyday work environment.&lt;/p>
&lt;p>After the initial idea, when I started to think more about my requirements, I quickly realized that I want something generic.&lt;/p>
&lt;p>First, I want to install a different set of packages depending on where I am installing them: containers, WSL instances, or real machines.
My laptop needs slightly different settings compared to my desktop.&lt;/p>
&lt;p>Second, I want to be able to do this on multiple distributions.
Previously it was really annoying when I had to debug a distro-specific bug, unless it happened to involve one of my primary Linux distributions.
I am also using a different OS on my work laptop and personal desktop PC because of company requirements.&lt;/p>
&lt;p>With a proper Ansible setup, I can make all of these work seamlessly, even autodetecting the environment, and verifying all important configurations on CI for every commit.&lt;/p>
&lt;p>Your requirements will most likely be different.
Think about these beforehand and structure your repository accordingly!&lt;/p>
&lt;h3 id="containers-or-virtual-machines">Containers or virtual machines?&lt;/h3>
&lt;p>So far I mentioned both as alternatives, and both have their pros and cons.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Aspect&lt;/th>
&lt;th>Container&lt;/th>
&lt;th>Virtual machine&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Resource overhead&lt;/td>
&lt;td>Low&lt;/td>
&lt;td>Higher&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Spin-up time&lt;/td>
&lt;td>Seconds&lt;/td>
&lt;td>Minutes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Host integration (mounts, networks)&lt;/td>
&lt;td>Easy, direct&lt;/td>
&lt;td>Network only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Isolation from host&lt;/td>
&lt;td>Partial&lt;/td>
&lt;td>Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GUI / IDE support&lt;/td>
&lt;td>Limited, terminal-friendly&lt;/td>
&lt;td>Full desktop&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Privileged tools (GDB, GPU)&lt;/td>
&lt;td>Extra capabilities required&lt;/td>
&lt;td>Native, inside the VM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Credential storage&lt;/td>
&lt;td>Shares host’s filesystem&lt;/td>
&lt;td>Must duplicate (SSH key, hardware key)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>For now, I went with containers.
With a few helper scripts I can mount specific directories from the host OS, and I can also specify which docker/podman network the new container should join.
This lets me start up my docker-compose development clusters directly on my main OS, and lets the agent access the development/test database and other containers for its work using the shared network.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">dcont run --mount `pwd` --network hackorum_default --context main-dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I even have a &lt;code>context&lt;/code> parameter, which lets me keep multiple independent AI configurations: different system-level CLAUDE.md, plugin set, hooks, and so on.
Underneath, this is just a few specific mounts and symlinks, but the advantage is huge:&lt;/p>
&lt;ul>
&lt;li>I can quickly experiment without fearing that I’ll break my main workflows&lt;/li>
&lt;li>I have completely separate setups for development and review work, without them conflicting with each other&lt;/li>
&lt;/ul>
&lt;p>The upsides are easy integration, lower resource overhead, and quicker spin-up.
I can mount directories directly from the host, and easily interact with docker containers running on the host.&lt;/p>
&lt;p>The downside comes from that same integration:
everything is still on the host, and the more access you give to the container, the less secure it becomes.
Tools like GDB and GPU access require extra privileges, and you might have to relax SELinux features for the container.&lt;/p>
&lt;p>The privilege problem, and the possibility of giving the container too much access, is a real risk.
Docker, which runs as root on the host, and rootless podman, which maps the container root to the current host user, behave very differently if something is misconfigured – but neither protects the data accessible to the running user.&lt;/p>
&lt;p>You can tighten the defaults with flags like &lt;code>--cap-drop=ALL&lt;/code>, &lt;code>--security-opt=no-new-privileges&lt;/code>, and read-only mounts where possible, but these only narrow the attack surface; they don’t fix what you mount in.
Which means what you mount matters more than which runtime you pick.&lt;/p>
&lt;h4 id="mounts-and-credentials">Mounts and credentials&lt;/h4>
&lt;p>&lt;code>.env&lt;/code> files, for example, can be challenging:
these can contain API keys, passwords, and other secrets required by the application, which ideally shouldn’t be accessible to the coding agent.
I started using two levels of them – one in the project folder with only generic data, and another one level above containing sensitive login information for external services.
This way, when I mount the project folder, the container can’t access the sensitive &lt;code>.env&lt;/code> file.&lt;/p>
&lt;p>There are also some special files to watch out for:
mounting &lt;code>/var/run/docker.sock&lt;/code> into the container, for example, can break the sandbox completely, as it grants access equivalent to root on the host.&lt;/p>
&lt;h4 id="when-to-pick-a-vm-instead">When to pick a VM instead&lt;/h4>
&lt;p>A container also isn’t a full-fledged desktop.
Personally, I am used to working in terminals; I like tools like tmux or neovim.
But if you prefer desktop applications and IDEs, a full virtual machine might be a better option.&lt;/p>
&lt;p>Full virtual machines aren’t more difficult to set up and give you a complete GUI, but they raise a different question:
how do you set everything up without accidental credential leaks?&lt;/p>
&lt;p>You either rely on network synchronization between your main OS and the virtual machine – pushing to remotes only from the main OS – or you give the virtual machine a hardware key and store your SSH key on it.&lt;/p>
&lt;p>Agents can of course always access and leak their own API keys; we can’t do anything about that with 100% certainty.
But we can aim to reduce their ability to leak anything else, by minimizing what they physically have access to.&lt;/p>
&lt;h3 id="an-example-setup">An example setup&lt;/h3>
&lt;p>You can check out my &lt;a href="https://github.com/dutow/dotfiles" target="_blank" rel="noopener noreferrer">dotfiles&lt;/a> for inspiration.
It should be only that:
something you can look at while designing your own version.&lt;/p>
&lt;p>It is designed for my workflows, and yours are most likely different.
You also shouldn’t blindly trust a script somebody else’s LLM generated.&lt;/p>
&lt;h4 id="the-helper-script">The helper script&lt;/h4>
&lt;p>The repository has a readme; the most interesting part is probably &lt;a href="https://github.com/dutow/dotfiles/blob/master/dcont" target="_blank" rel="noopener noreferrer">the script I mentioned earlier&lt;/a>, which builds and runs the containers.&lt;/p>
&lt;p>It is long and complex, and deals with additional details I didn’t even mention here, to keep this introduction from getting too involved.&lt;/p>
&lt;p>The basic idea, however, is easy to summarize.
A basic docker command is simple:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker run -it ubuntu:latest /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But it also gets complicated quickly:&lt;/p>
&lt;ul>
&lt;li>what folders need mounting? (the project, specific directories for tools)&lt;/li>
&lt;li>which networks to join?&lt;/li>
&lt;li>do we have to set up specific hardware, like a GPU?&lt;/li>
&lt;li>do we need specific access permissions for some software?&lt;/li>
&lt;li>and so on&lt;/li>
&lt;/ul>
&lt;p>The command quickly becomes longer and longer, and copy-pasting it from notes or shell history isn’t fun.
It is also most likely project-specific.
You want different mounts, different contexts for AIs, different specific permissions.
All this should be configurable, and still simple.&lt;/p>
&lt;p>In my case, most of my projects also have &lt;code>.env&lt;/code> files set up, which makes it a no-brainer to also support configuration through environment variables.&lt;/p>
&lt;p>Most of the time, all I have to do is &lt;code>cd&lt;/code> into the project directory and execute &lt;code>dcont&lt;/code> without any extra parameters. That starts up a ready-to-use, project-specific setup, and I can immediately start typing instructions to Claude.&lt;/p>
&lt;h4 id="the-ansible-part">The Ansible part&lt;/h4>
&lt;p>I already mentioned this before, but didn’t go into the details:
you can build docker or podman images with Ansible.&lt;/p>
&lt;p>Normally this isn’t that useful:
if the only goal is a container cluster, a Containerfile is much easier to use, and more efficient for rebuilding, since it automatically detects which layers have to be rebuilt.&lt;/p>
&lt;p>If the goal, however, is to replicate the same setup on a real host and in a container, the picture is different.
These images are only meant for local use, so layering and image size don’t matter – we’ll never upload them.&lt;/p>
&lt;p>Build times are also secondary.
Even if a rebuild is needed once or twice a day, you can continue using the previous version in the meantime and switch later.
And it’s not like we can’t do proper incremental builds with it; Ansible supports that too – it’s just a bit slower than how containers normally do it.&lt;/p>
&lt;p>This is included in the same script, and the solution is surprisingly simple:&lt;/p>
&lt;ol>
&lt;li>start a container with &lt;code>sleep infinity&lt;/code>&lt;/li>
&lt;li>copy the dotfiles repo into it, since it’s already checked out on the host&lt;/li>
&lt;li>run the same dotfiles/Ansible script as on other hosts (with proper parameters)&lt;/li>
&lt;li>set up a proper user&lt;/li>
&lt;li>commit the image&lt;/li>
&lt;/ol>
&lt;p>The same could be done using a &lt;code>Containerfile&lt;/code>, but what’s the advantage?
The image isn’t shareable or reusable anyway, and some operations are easier to implement directly in bash.
This process also leaves open the possibility of doing incremental builds, instead of always rerunning the installation script from scratch.&lt;/p>
&lt;h3 id="the-unsaid-part-network-access">The unsaid part: network access&lt;/h3>
&lt;p>In all of the sandboxing discussion above, I quietly ignored the question of network access:
if you give unrestricted network access to an LLM agent, you can have a bad time.&lt;/p>
&lt;p>Prompt injection exists, even if AI companies try to make it harder and harder.&lt;/p>
&lt;p>For most use cases, a complete network ban is also a bad idea for productivity and code quality, which makes this another complex, open-ended question with its own options and tradeoffs – out of scope for this already long blog post.&lt;/p>
&lt;p>I hope the information I provided here was useful, and that you can improve your AI setup based on it!&lt;/p></content:encoded><author>Zsolt Parragi</author><category>AI</category><category>development</category><media:thumbnail url="https://percona.community/blog/2026/05/ai-gardening-main_hu_93517c5ece17ad80.jpg"/><media:content url="https://percona.community/blog/2026/05/ai-gardening-main_hu_2be41d9d358fb11a.jpg" medium="image"/></item><item><title>InnoDB Redo Log Sizing: Stop Guessing, Start Measuring</title><link>https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/</link><guid>https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/</guid><pubDate>Sat, 02 May 2026 00:00:00 UTC</pubDate><description>Introduction Many MySQL configurations inherit redo log sizing from defaults, aging blog posts, or configuration folklore.</description><content:encoded>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Many MySQL configurations inherit redo log sizing from defaults, aging blog posts, or configuration folklore.&lt;/p>
&lt;p>&lt;code>innodb_redo_log_capacity&lt;/code> gets set once… and then quietly fades into the background.&lt;/p>
&lt;p>But redo log capacity directly shapes how efficiently MySQL absorbs writes, manages checkpoint pressure, and handles burst-heavy workloads.&lt;/p>
&lt;p>Set it too low, and aggressive flushing can throttle throughput.&lt;br>
Set it too high, and crash recovery can become painfully long.&lt;/p>
&lt;p>Redo logs are more than crash insurance.&lt;/p>
&lt;p>They are part of your write-performance architecture.&lt;/p>
&lt;blockquote>
&lt;p>Redo logs are the shock absorbers of write-heavy MySQL. Too small, and performance jolts. Too large, and recovery drags.&lt;/p>&lt;/blockquote>
&lt;h2 id="why-redo-logs-matter">Why Redo Logs Matter&lt;/h2>
&lt;p>InnoDB redo logs are often described as crash recovery journals, but that description undersells their real operational value.&lt;/p>
&lt;p>Redo logs function as a write buffer between committed transactions and eventual data file writes.&lt;/p>
&lt;p>When a transaction commits:&lt;/p>
&lt;ul>
&lt;li>Changes are written to the redo log first&lt;/li>
&lt;li>Dirty pages remain in memory&lt;/li>
&lt;li>Data pages are flushed later&lt;/li>
&lt;/ul>
&lt;p>This write-ahead logging (WAL) design allows MySQL to:&lt;/p>
&lt;ul>
&lt;li>Absorb bursts of write activity&lt;/li>
&lt;li>Reduce immediate random disk writes&lt;/li>
&lt;li>Smooth checkpoint behavior&lt;/li>
&lt;li>Preserve durability&lt;/li>
&lt;/ul>
&lt;p>Redo logs act like pressure regulators in a write-heavy system.&lt;/p>
&lt;p>They absorb pressure spikes so the entire system doesn’t thrash every time demand increases.&lt;/p>
&lt;p>Without enough redo capacity, MySQL has less room to absorb write bursts before it must flush aggressively.&lt;/p>
&lt;h2 id="checkpoint-age-and-flushing-pressure">Checkpoint Age and Flushing Pressure&lt;/h2>
&lt;p>Redo log sizing becomes most visible when checkpoint pressure builds.&lt;/p>
&lt;p>Checkpoint age represents how far current write activity has advanced beyond the last durable checkpoint:&lt;/p>
&lt;p>&lt;code>Checkpoint Age = Current LSN - Last Checkpoint LSN&lt;/code>&lt;/p>
&lt;p>As checkpoint age approaches total redo capacity:&lt;/p>
&lt;ul>
&lt;li>Adaptive flushing intensifies&lt;/li>
&lt;li>Page cleaners become more aggressive&lt;/li>
&lt;li>Dirty pages flush faster&lt;/li>
&lt;li>Disk I/O spikes&lt;/li>
&lt;li>Latency often becomes unstable&lt;/li>
&lt;/ul>
&lt;p>This is where undersized redo logs can trigger flush storms.&lt;/p>
&lt;blockquote>
&lt;p>MySQL isn’t writing more data. It’s being forced to write sooner and less efficiently.&lt;/p>&lt;/blockquote>
&lt;h3 id="useful-metrics">Useful metrics&lt;/h3>
&lt;ul>
&lt;li>&lt;code>Innodb_checkpoint_age&lt;/code>&lt;/li>
&lt;li>&lt;code>Innodb_buffer_pool_pages_dirty&lt;/code>&lt;/li>
&lt;li>&lt;code>Innodb_data_fsyncs&lt;/code>&lt;/li>
&lt;li>&lt;code>Innodb_log_waits&lt;/code>&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>When redo space shrinks, MySQL doesn’t stop writing. It starts panicking earlier.&lt;/p>&lt;/blockquote>
&lt;h2 id="symptoms-of-undersized-redo">Symptoms of Undersized Redo&lt;/h2>
&lt;p>Small redo logs rarely announce themselves directly.&lt;/p>
&lt;p>Instead, they often masquerade as generalized storage or write-performance issues.&lt;/p>
&lt;h3 id="common-warning-signs">Common warning signs&lt;/h3>
&lt;ul>
&lt;li>Periodic write stalls&lt;/li>
&lt;li>Spikes in fsync activity&lt;/li>
&lt;li>Sharp increases in page cleaner workload&lt;/li>
&lt;li>TPS drops during burst traffic&lt;/li>
&lt;li>Dirty page percentage volatility&lt;/li>
&lt;li>Stable CPU, unstable write latency&lt;/li>
&lt;/ul>
&lt;h3 id="a-common-misdiagnosis">A common misdiagnosis&lt;/h3>
&lt;p>Many systems blame disks when the real issue is insufficient redo headroom.&lt;/p>
&lt;p>If writes are arriving faster than redo can comfortably buffer them, MySQL is forced into reactive flushing patterns.&lt;/p>
&lt;p>The problem may not be disk speed.&lt;/p>
&lt;p>It may be timing pressure.&lt;/p>
&lt;h2 id="measuring-with-status-counters">Measuring with Status Counters&lt;/h2>
&lt;p>Redo log sizing should be based on observed workload, not memory percentages or inherited defaults.&lt;/p>
&lt;h3 id="step-1-measure-redo-generation-rate">Step 1: Measure redo generation rate&lt;/h3>
&lt;p>Use:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENGINE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">INNODB&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Track:&lt;/p>
&lt;ul>
&lt;li>Log sequence number&lt;/li>
&lt;li>Log flushed up to&lt;/li>
&lt;li>Last checkpoint at&lt;/li>
&lt;/ul>
&lt;p>Measure LSN growth over time:&lt;/p>
&lt;p>&lt;code>Redo Generation Rate = (LSN delta) / elapsed time&lt;/code>&lt;/p>
&lt;h3 id="example">Example&lt;/h3>
&lt;p>If LSN grows by 4 GB over one hour:&lt;/p>
&lt;ul>
&lt;li>1 GB redo capacity = frequent pressure&lt;/li>
&lt;li>4 GB redo capacity = ~1 hour buffer&lt;/li>
&lt;li>8 GB redo capacity = larger burst tolerance&lt;/li>
&lt;/ul>
&lt;h3 id="step-2-watch-for-log-stress">Step 2: Watch for log stress&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_log_waits'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_os_log%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="key-metric">Key metric&lt;/h3>
&lt;p>&lt;code>Innodb_log_waits&lt;/code>&lt;/p>
&lt;p>If this value increases, transactions are waiting for log free space.&lt;/p>
&lt;p>That is one of the clearest signs your redo logs may be too small.&lt;/p>
&lt;p>&lt;code>Innodb_log_waits&lt;/code> is less a tuning suggestion and more a smoke alarm.&lt;/p>
&lt;h2 id="practical-sizing-strategy">Practical Sizing Strategy&lt;/h2>
&lt;p>Forget percentage-of-RAM formulas.&lt;/p>
&lt;p>Redo logs should be sized around workload intensity.&lt;/p>
&lt;p>&lt;strong>A practical starting point:&lt;/strong>&lt;/p>
&lt;p>Size redo capacity to hold 30 to 60 minutes of peak redo generation&lt;/p>
&lt;h3 id="example-1">Example&lt;/h3>
&lt;p>Peak redo generation = 6 GB/hour&lt;/p>
&lt;p>&lt;strong>Minimum:&lt;/strong>&lt;/p>
&lt;p>30 minutes = 3 GB&lt;/p>
&lt;p>&lt;strong>Safer:&lt;/strong>&lt;/p>
&lt;p>60 minutes = 6 GB&lt;/p>
&lt;p>&lt;strong>Heavy burst environments:&lt;/strong>&lt;/p>
&lt;p>Larger sizing may reduce flush volatility further&lt;/p>
&lt;h2 id="trade-offs">Trade-Offs&lt;/h2>
&lt;h3 id="smaller-redo-logs">Smaller Redo Logs&lt;/h3>
&lt;p>&lt;strong>Pros:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Faster crash recovery&lt;/li>
&lt;li>Lower storage footprint&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Increased checkpoint pressure&lt;/li>
&lt;li>More aggressive flushing&lt;/li>
&lt;li>Greater write instability&lt;/li>
&lt;/ul>
&lt;h3 id="larger-redo-logs">Larger Redo Logs&lt;/h3>
&lt;p>&lt;strong>Pros:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Better burst absorption&lt;/li>
&lt;li>Smoother sustained write performance&lt;/li>
&lt;li>Reduced flush storms&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Longer crash recovery&lt;/li>
&lt;li>Delayed visibility into pressure buildup&lt;/li>
&lt;/ul>
&lt;h2 id="common-mistakes">Common Mistakes&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Treating redo like buffer pool sizing&lt;/strong>
Redo capacity is about write throughput buffering, not memory caching.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Ignoring Innodb_log_waits&lt;/strong>
This can leave obvious pressure invisible until performance suffers.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Oversizing without testing recovery&lt;/strong>
Large redo logs may improve runtime but worsen restart scenarios.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Sizing for average load instead of peak&lt;/strong>
Redo logs exist to absorb pressure spikes, not calm periods.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>The right redo log size isn’t about maximizing a configuration value.&lt;/p>
&lt;p>It’s about matching capacity to workload behavior.&lt;/p>
&lt;ul>
&lt;li>Too small, and MySQL becomes reactive.&lt;/li>
&lt;li>Too large, and crash recovery becomes the hidden tax.&lt;/li>
&lt;/ul>
&lt;p>When redo logs are properly sized, they fade into the background.&lt;/p>
&lt;p>They quietly absorb bursts, smooth checkpoint behavior, and preserve performance consistency under pressure.&lt;/p>
&lt;blockquote>
&lt;p>Redo logs work best when they disappear into the background, quietly absorbing pressure instead of creating it.&lt;/p>&lt;/blockquote>
&lt;p>Stop guessing.&lt;/p>
&lt;p>Measure your workload, observe your log pressure, and size with intent.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>OpenSource</category><category>Percona</category><category>MySQL</category><category>InnoDB</category><category>Performance</category><category>Database Tuning</category><media:thumbnail url="https://percona.community/blog/2026/05/innodb-redo-log-sizing_hu_73a0a44550e397cc.jpg"/><media:content url="https://percona.community/blog/2026/05/innodb-redo-log-sizing_hu_14354ff27e6a3550.jpg" medium="image"/></item><item><title>Open source doesn’t die. It gets unfunded.</title><link>https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/</link><guid>https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/</guid><pubDate>Thu, 30 Apr 2026 11:00:00 UTC</pubDate><description>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.</description><content:encoded>&lt;p>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.&lt;/p>
&lt;h2 id="archived-you-mean-eol">Archived, &lt;a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/comment/oilzdag/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button" target="_blank" rel="noopener noreferrer">you mean EOL&lt;/a>?&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1418" height="350" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_fbe3f4e41db1c49c.webp 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_daf0f1b26a7356c8.webp 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_9a813996646fa88a.webp 1400w" src="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_9a813996646fa88a.webp" alt="blog/2026/04/opensourcedoesntdie-reddit.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>No! Open source software rarely has a hard “end of life.” What it does have are maintainership gaps and those can be just as serious.&lt;/p>
&lt;p>It’s different when PostgreSQL community announces a major version EOL. This happens because Community chooses to not to support it and move on to focus on newer versions.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_4355fde0272c0709.webp 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_6d8b8e0e639f7495.webp 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_65c6d17dd0fd1a8d.webp 1400w" src="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_65c6d17dd0fd1a8d.webp" alt="blog/2026/04/opensourcedoesntdie-thisisopensource.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Reading the message from David Steele, the long-time primary maintainer of pgBackRest you will not find “end of life” term. The project is marked read-only and no longer actively maintained, but that is not the same as being permanently dead.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1890" height="1762" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_e5cec2faf8b498dc.webp 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_4671b2f39ae53ef3.webp 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_3f66e23a0cf5d417.webp 1400w" src="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_3f66e23a0cf5d417.webp" alt="blog/2026/04/opensourcedoesntdie-maintenance.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>pgBackRest is not “end of life.” There is no governing body declaring support ended. What happened is simpler and more common in open source: the maintainer can no longer afford to continue.&lt;/p>
&lt;h2 id="so-what-happened-then">So what happened then?&lt;/h2>
&lt;p>This requires some story telling and I don’t think that I can do it better than &lt;a href="https://mydbanotebook.org/about/" target="_blank" rel="noopener noreferrer">Lætitia Avrot&lt;/a> already did in her &lt;a href="https://mydbanotebook.org/posts/pgbackrest-is-dead.-now-what/#what-happened" target="_blank" rel="noopener noreferrer">blogpost&lt;/a> (though I do not like the title):&lt;/p>
&lt;blockquote>
&lt;p>Crunchy Data, which had sponsored &lt;code>pgBackRest&lt;/code> for most of its life and employed David, was sold. After that, David spent months looking for a position that would let him keep working on the project. He also tried to secure independent sponsorship. Neither worked out. He needs to make a living. The project requires sustained effort which he can no longer provide without being paid for it.&lt;/p>&lt;/blockquote>
&lt;p>This is the issue. An experienced developer, who wants to work on the project (that a big chunk of enterprises use) finds himself to be the “Nebraska guy” from &lt;a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">XKCD comic&lt;/a>.&lt;/p>
&lt;p>When you look at the situation we’re in this is the classic “Nebraska guy problem”: critical infrastructure maintained by a single person. pgBackRest is widely used in production, yet its sustainability dependson one individual being able to justify working on it. That does not seem fair and David did right to point this out with his move.&lt;/p>
&lt;p>Of course, if anyone in the community chose to, they can still maintain the project by forking it. But why, since the problem is elsewhere?&lt;/p>
&lt;p>Most people understand that engineers need to be paid for their work. What not everyone realizes is that the free for all software that the open source license provides does not mean free as in beer. Someone still needs to fund it!&lt;/p>
&lt;p>Unfortunately “someone” almost certainly is going to be “no-one” unless “anyone” realizes they are going to miss the software if nobody maintains it anymore.&lt;/p>
&lt;p>While there’s a claim to be made that:&lt;/p>
&lt;blockquote>
&lt;p>Companies are as good as they have to and as bad as they are allowed to&lt;/p>&lt;/blockquote>
&lt;p>And often we see that an entity uses software they do not have to pay license fees for, treating this as cost optimization. There is also a large chunk of organizations that realize this is not a good long term strategy. Actively lowering the operational risk is important.&lt;/p>
&lt;p>This is where foundations typically kick in: providing an easy way for organizations to contribute and ensure the longevity and healthiness of the projects. But PostgreSQL does not (yet) have one.&lt;/p>
&lt;h2 id="where-are-we-now">Where are we now?&lt;/h2>
&lt;p>There’s a lot of backchannel talks happening, join them and represent the open source point of view.&lt;/p>
&lt;p>One example of such a channel is &lt;a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/pgbackrest_is_no_longer_being_maintained/" target="_blank" rel="noopener noreferrer">Reddit&lt;/a> though it requires quite a lot of karma (a Reddit thing) and not everyone will find it easy to successfully post there.&lt;/p>
&lt;p>While there you can join Telegram, Slack or Discord discussions some of the channels there are private so we wanted to provide an open and visible place where you can let us know what is your stance and for this purpose &lt;a href="https://forums.percona.com/t/pgbackrest-is-eol/40720" target="_blank" rel="noopener noreferrer">(Percona Community Forum thread is available)&lt;/a>.&lt;/p>
&lt;p>A lot of blog posts have been written on this subject, check out &lt;a href="https://planet.postgresql.org/" target="_blank" rel="noopener noreferrer">Planet PostgreSQL&lt;/a> to find some of them! I particularly enjoyed some of them, the &lt;a href="https://proopensource.it/blog/postgresql-ecosystem-problems-2026" target="_blank" rel="noopener noreferrer">one&lt;/a> from &lt;a href="https://proopensource.it/stefanie-janine-stoelting.html" target="_blank" rel="noopener noreferrer">Stefanie Janine Stölting&lt;/a>, I feel I am mostly aligned with. PostgreSQL needs an Ecosystem Umbrella Foundation&lt;/p>
&lt;h2 id="the-future-of-open-source-is-on-us">The future of open source is on us&lt;/h2>
&lt;p>Reading that a project is EOL is triggering to me. When long-time maintainer announced plans to step away after more than a decade of work, instead of focusing on what the problem is that caused him to do so and how to solve the issue.Naming it “dead” complicate things even further. Labeling the project as “dead” doesn’t solve the problem. Rather, it accelerates the wrong response. Users start looking for replacements instead of asking how to sustain the project.&lt;/p>
&lt;p>This is not the way, young Padawan!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_3ce25c0246a913e9.webp 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_1bf1c2e57ba836e1.webp 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_2d04686e1ca153e9.webp 1400w" src="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_2d04686e1ca153e9.webp" alt="blog/2026/04/opensourcedoesntdie-youngpadawan.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We need a body that helps both users and authors by:&lt;/p>
&lt;ol>
&lt;li>Providing governance and a helping hand to the ecosystem. Yes, this is also funding&lt;/li>
&lt;li>Providing guarantees of healthiness. This means users will have it easier to know the tools are in good shape.&lt;/li>
&lt;/ol>
&lt;h2 id="so-whats-with-pgbackrest">So what’s with pgBackRest&lt;/h2>
&lt;p>While we talk here in the public, a lot of decisions are being made and Percona among other companies is working towards resolving this situation.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_490eac7afd0d55de.webp 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_bd96d664f17e89b9.webp 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_ba1a21dd7e603445.webp 1400w" src="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_ba1a21dd7e603445.webp" alt="blog/2026/04/opensourcedoesntdie-allyouneed.png" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Have patience. Work is already underway behind the scenes, and the situation is evolving. There will be positive news resolving the situation coming soon, as Open Source doesn’t die!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pgBackRest</category><media:thumbnail url="https://percona.community/blog/2026/04/opensourcedoesntdie-blog-hero-fundit_hu_8aeb6448363e052b.jpg"/><media:content url="https://percona.community/blog/2026/04/opensourcedoesntdie-blog-hero-fundit_hu_a2b745b696895b70.jpg" medium="image"/></item><item><title>OIDC error scenarios</title><link>https://percona.community/blog/2026/04/30/oidc-error-scenarios/</link><guid>https://percona.community/blog/2026/04/30/oidc-error-scenarios/</guid><pubDate>Thu, 30 Apr 2026 00:00:00 UTC</pubDate><description>Last time, in OIDC in PostgreSQL: With Keycloak, we created a working demo setup that was able to successfully authenticate a user using OIDC.</description><content:encoded>&lt;p>Last time, in &lt;a href="https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/">OIDC in PostgreSQL: With Keycloak&lt;/a>, we created a working demo setup that was able to successfully authenticate a user using OIDC.&lt;/p>
&lt;p>In this blog post, we follow the same example, but instead of the success story, we explore how OAuth keeps our PostgreSQL servers secure.&lt;/p>
&lt;p>We won’t focus on complex attack vectors, like the examples in the &lt;a href="https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/">second blog post&lt;/a> in the OIDC series.
Instead of social engineering, we’ll look at practical errors, misconfigurations and honest mistakes - understanding error messages and how to fix them.&lt;/p>
&lt;h3 id="improved-test-setup">Improved test setup&lt;/h3>
&lt;p>Along with the step by step tutorial, previously we also linked a &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/tree/main/examples/keycloak" target="_blank" rel="noopener noreferrer">docker/podman compose configuration&lt;/a>.
This is still available, and we even improved it for testing the error scenarios.&lt;/p>
&lt;p>If you want to update the configuration manually instead, this is what changed: we duplicated everything!&lt;/p>
&lt;ul>
&lt;li>Instead of a single testuser, we have two: &lt;code>testuser&lt;/code> and &lt;code>testuser2&lt;/code>, both using the same &lt;code>asdfasdf&lt;/code> password&lt;/li>
&lt;li>Instead of one client, we have two: &lt;code>pgtest&lt;/code> and &lt;code>pgtest2&lt;/code>&lt;/li>
&lt;li>Instead of one scope, we have two: &lt;code>pgscope&lt;/code>, &lt;code>pgscope2&lt;/code>&lt;/li>
&lt;li>Instead of one realm, we have two - containing exactly the same setup: &lt;code>pgrealm&lt;/code> and &lt;code>wrongrealm&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>A role named &lt;code>pgrole&lt;/code> is also defined.
The &lt;code>pgtest2&lt;/code> client and &lt;code>pgscope2&lt;/code> scope both require the &lt;code>pgrole&lt;/code> role.
Only &lt;code>testuser2&lt;/code> is assigned this role; &lt;code>testuser&lt;/code> does not have it.&lt;/p>
&lt;p>The following table summarizes the access matrix:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>pgtest&lt;/th>
&lt;th>pgtest2&lt;/th>
&lt;th>pgscope&lt;/th>
&lt;th>pgscope2&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>testuser&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>denied&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>denied&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>testuser2&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>OK&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="success-despite-a-fatal-error">Success despite a FATAL error?&lt;/h3>
&lt;p>However, before we start using all these additional items, let’s go back to the end of the Keycloak story, where we succeeded in logging in.
Or did we?
While &lt;code>psql&lt;/code> logged us in, if we checked the server error log, we could see the following there:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But if we are observant enough, this message is logged before we even go to the device authentication website of Keycloak, and enter the authentication code.
This isn’t a real error, it’s just a side effect of how OAuth is implemented internally, and will most likely be fixed in PostgreSQL 19, the FATAL message will no longer show up.&lt;/p>
&lt;p>As for PostgreSQL 18, unfortunately, we have to live with this.
This also means that we can’t rely on simply looking for OAuth authentication errors in the server log, because all OAuth authentication failures will result in exactly the same message, no matter if they are logged because of this harmless situation or because of a real authentication issue.&lt;/p>
&lt;p>A workaround is to rely on the validators instead: since the server is unaware of the exact error situation anyway – it delegates validation to the validator – these plugins will print out much more detailed &lt;em>log messages&lt;/em>.
We’ll see some examples later with pg_oidc_validator, as we explore the error scenarios.&lt;/p>
&lt;p>However, keep in mind that the sentence above says &lt;em>log messages&lt;/em>, and not &lt;em>errors&lt;/em> or &lt;em>fatal errors&lt;/em>.
Validators are not allowed to print out ERROR and FATAL messages for authentication failures, so users have to look for WARNING or LOG level messages for the details.
In practice, PostgreSQL will still print the same generic FATAL error message about OAuth bearer authentication failing – but it will appear after the validator-specific WARNING or LOG messages that contain the actual diagnostic information.&lt;/p>
&lt;h3 id="why-does-it-happen">Why does it happen?&lt;/h3>
&lt;p>Earlier we already established that PostgreSQL validates that the client and the server use the same issuer, but didn’t go into more detail than this.&lt;/p>
&lt;p>Usually when a service validates user input, it does so on the server.
The main reason for this is that developers can trust the backend, controlled by administrators, while they can’t trust the frontend, potentially used by malicious users.&lt;/p>
&lt;p>But are we validating user input in this case?
Why does the user even have to specify the issuer, since the server already knows it, it’s in the HBA configuration?&lt;/p>
&lt;p>Because this check isn’t the server validating the user, it’s the opposite:
the user validating the server.&lt;/p>
&lt;ol>
&lt;li>The client sends an empty connection request to the server.&lt;/li>
&lt;li>The server confirms that we are using OAuth, and sends back its issuer URL.&lt;/li>
&lt;li>The client checks whether the issuer it is planning to use – or has already used, if it already has a valid token – matches the one sent by the server.
&lt;ul>
&lt;li>If it doesn’t match, it aborts the login attempt and prints an error.&lt;/li>
&lt;li>If it does match, it continues with a real authentication attempt.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
sequenceDiagram
participant C as psql (Client)
participant S as PostgreSQL Server
participant K as Keycloak
C->>S: Connection request (no token)
S-->>S: No token, auth fails
Note right of S: FATAL logged here (harmless side effect)
S->>C: OAuth challenge + issuer URL
C-->>C: Compare issuer URL with oauth_issuer
alt Issuer mismatch
C-->>C: Abort with error
else Issuer matches
C->>K: Device authorization flow
K->>C: Access token
C->>S: Connection request (with token)
S->>S: Validator checks token
S->>C: Authentication success
end
&lt;/pre>
&lt;p>The FATAL error in the server log is a side effect of the first empty authentication attempt, that wasn’t fixed in time before the PG18 release.&lt;/p>
&lt;h3 id="why-do-we-need-this-check">Why do we need this check?&lt;/h3>
&lt;p>With the improved configuration, we can test what happens when we specify the wrong issuer:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bin/psql -h 127.0.0.1 'dbname=postgres oauth_issuer=https://keycloak:8443/realms/wrongrealm oauth_client_id=pgtest'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql: error: connection to server at "127.0.0.1", port 5432 failed: server's discovery document at https://keycloak:8443/realms/pgrealm/.well-known/openid-configuration (issuer "https://keycloak:8443/realms/pgrealm") is incompatible with oauth_issuer (https://keycloak:8443/realms/wrongrealm)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Notice that compared to the correct command, which had the pgrealm, we are using the other Keycloak realm.
And if we check the server log, we can see that there are no additional log messages there – we see a single OAuth FATAL error, which is not a real error, just the side effect we are investigating.
This error is entirely on the client side.&lt;/p>
&lt;p>And that brings us back to the question:
why do we need this?&lt;/p>
&lt;p>On one hand, it helps us prevent honest mistakes early.
In our example, wrongrealm and pgrealm are exactly the same – they have users with the same name, scopes with the same names, clients with the same names.
If there’s a misconfiguration, and the server and the client use different realms in a similar setup, everything would seem to work – the user trying to log in would be able to log in, get a token, psql would send it to the server…
and then on the server the validator would reject it – assuming that it is a good validator, like pg_oidc_validator.
No harm done – other than disclosing a token to the server that shouldn’t have been sent there –, but figuring out what the problem is could take a while.&lt;/p>
&lt;p>On the other hand: what if we aren’t dealing with a malicious user, but a malicious server?&lt;/p>
&lt;p>In the previous attack vectors we showcased, the attacker was always a third party:
somebody who wanted to steal access to the database server.&lt;/p>
&lt;p>But we don’t necessarily need a different unknown adversary, it could be the server we are using:
do we absolutely know and trust its administrators?
Sometimes yes, sometimes no.&lt;/p>
&lt;p>Those administrators might be aware that we are also using OAuth for something else, and might plot to gain access to it.
So instead of sending us the issuer we expect, the server sends us something else – for example a spoofed site, tricking us to complete login into a different service.&lt;/p>
&lt;p>Remember the earlier situation where the Fake Photo Gallery Website used Client ID spoofing to gain access to the PostgreSQL Database?
This situation is basically the same – the only difference is that this time PostgreSQL Database is trying to gain access to Photo Gallery.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant U as User (psql)
participant M as Malicious PostgreSQL Server
participant SSO as Other service
U->>M: Connection request
M->>U: OAuth challenge + spoofed issuer URL (instead of expected issuer)
Note over U: Without issuer check, user proceeds
U->>SSO: Authenticates, thinking it is for PostgreSQL
SSO->>U: Access token (valid for a different service)
U->>M: Sends token to server
Note over M: Malicious server now holds a token valid for the other service
&lt;/pre>
&lt;p>The client-side issuer check prevents this: psql compares the issuer URL from the server against the &lt;code>oauth_issuer&lt;/code> it was configured with, and aborts if they don’t match.&lt;/p>
&lt;p>While requiring the client to specify the issuer may seem redundant, it’s an important safeguard that prevents tokens from being disclosed to malicious servers.&lt;/p>
&lt;h3 id="can-we-verify-the-validator">Can we verify the validator?&lt;/h3>
&lt;p>If we specify an incorrect issuer, the client rejects it before completing the OAuth flow – that’s great, but this means the validator isn’t part of the picture.
Can we even test that a validator handles this situation correctly, to verify that it properly rejects an attempt with an incorrect issuer?
Security-aware users might want to double check that somebody using a modified psql is also properly rejected.&lt;/p>
&lt;p>This is possible: in our next blog post, we’ll see how to implement custom clients outside psql, possibly using other OAuth flows.
In that scenario, we’ll be able to send custom tokens to the server, which has many uses – one of which is internal testing of OAuth validators.&lt;/p>
&lt;p>Rest assured, pg_oidc_validator handles this correctly – and we’ll show you how to verify it yourself in the next post.
If you are using a different validator, stay tuned to see how you can verify it!&lt;/p>
&lt;p>With pg_oidc_validator, you will see something like this in the server log:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">WARNING: OAuth validation failed with exception: claim value does not match expected value
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here “claim value does not match expected value” means that a field (claim) in the JWT doesn’t match our expectation.
While this might seem generic, currently pg_oidc_validator only validates exactly one field in this way: the issuer.&lt;/p>
&lt;p>On the client side, you can see the following generic error message:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Connection error: connection to server at "127.0.0.1", port 5432 failed: retrying connection with new bearer token
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">connection to server at "127.0.0.1", port 5432 failed: FATAL: OAuth bearer authentication failed for user "testuser"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Which is generally true for most OAuth errors – validators are expected not to provide detailed information about why they reject a connection back to the client, to limit the information available to potential attackers.&lt;/p>
&lt;h3 id="signature-failure">Signature failure&lt;/h3>
&lt;p>For some it might be surprising that we are getting an error about the issuer, and not about the token.
Why is that?&lt;/p>
&lt;p>The reason we use JWTs for access tokens is because they are cryptographically signed tokens.
While they contain the payload in clear text, the token ends with a signature, a proof that it was generated by the issuer we trust.&lt;/p>
&lt;p>This means that if the token was generated by a different issuer, it is signed by a different key.&lt;/p>
&lt;p>However, the order of operations inside the validator is different:
first we validate the fields in the cleartext data we have strong expectations about – in this case the issuer.
Then, after that’s valid, we also verify that the signature matches the public key of the issuer.&lt;/p>
&lt;p>Since in the above situation the issuer is different, we never get to the point of signature validation.&lt;/p>
&lt;p>To do that, somebody has to tamper with the token.
For example, an attacker realizes that we require a specific scope, and since JWTs contain everything in clear text, decides to edit the &lt;code>scp&lt;/code> claim and insert &lt;code>pgscope&lt;/code> into it.
In that situation, the issuer matches, the validator verifies the signature, and we end up with a different error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">WARNING: OAuth validation failed with exception: failed to verify signature: VerifyFinal failed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The client side error message didn’t change with this – this is clearly an attack attempt, we do not have to provide nice error messages for malicious users.&lt;/p>
&lt;h3 id="what-about-expired-tokens">What about expired tokens?&lt;/h3>
&lt;p>Another interesting scenario you might wonder about is token lifetime:
in OAuth, tokens have a limited period in which they are valid.&lt;/p>
&lt;p>PostgreSQL currently has no facilities to enforce token lifetime when a connection is active – once somebody is logged in, they stay logged in until they disconnect for some reason –, but validators are expected to validate that tokens are still valid at least during authentication.&lt;/p>
&lt;p>Similarly to the previous situation, testing this without a custom client isn’t possible, as psql always asks for a new token during the connection attempt, there is no way to send an earlier token with it.&lt;/p>
&lt;p>As in the previous example, this scenario is rejected by pg_oidc_validator, which logs the following message on the server:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">WARNING: OAuth validation failed with exception: token expired
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>On the client side, you can only see the same generic error message as before.&lt;/p>
&lt;p>While this doesn’t seem too user friendly, keep in mind that both of these errors can only happen with faulty clients.
Clients can, and should verify both the issuer and the expiration time before connecting to the server, and they should be able to provide nice error messages to the users based on that.&lt;/p>
&lt;h3 id="scope-mismatch">Scope mismatch&lt;/h3>
&lt;p>After the previous two situations, which are untestable with &lt;code>psql&lt;/code>, let’s move to the realm of errors which don’t require custom code.&lt;/p>
&lt;p>In the first and second blog posts we tried to emphasize how important scopes are in OAuth, how they can help prevent accidents.
Obviously, validators have to make sure that all the scopes the server asked for are present in the received token.
Having more scopes isn’t an issue – sometimes clients use the same token for multiple services –, but missing a required scope should be an error.&lt;/p>
&lt;p>To verify what happens in this situation, we can simply modify the pg_hba line to include a scope that doesn’t exist on the server, for example adding &lt;code>fooscope&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email fooscope",map=kcmap&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And then we can connect with psql as before:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bin/psql -h 127.0.0.1 'dbname=postgres oauth_issuer=https://keycloak:8443/realms/pgrealm oauth_client_id=pgtest'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Which should result in the following detailed error message in the server log:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">LOG: Authorization failed because of scope mismatch. Required scopes: email, fooscope, pgscope. Received scopes: email, pgscope, profile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LOG: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Validator failed to authorize the provided token.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email fooscope",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Similarly to the previous scenarios, this is completely validator specific, we can only showcase our validator.&lt;/p>
&lt;p>This scenario also depends on the OAuth flow used and the identity provider.
&lt;strong>Note:&lt;/strong> Keycloak, for example, permits unknown scopes for the device flow – it simply ignores them and returns the scopes it can.
However, it doesn’t do that for other flows – the Token Endpoint rejects unknown scopes with an error and doesn’t provide an access token.&lt;/p>
&lt;p>On the client side, the error is the same as before – no details about what’s missing.
Which is fine in this situation, as this is clearly a configuration error, something the administrators have to figure out and fix.&lt;/p>
&lt;p>Now let’s see the error slightly differently.&lt;/p>
&lt;p>The above example worked with the unmodified keycloak setup, described in the previous blog, but we have an improved test setup for this one.
Instead of using a non existent foo scope, let’s change our requirement to &lt;code>pgscope2&lt;/code>, which requires &lt;code>pgrole&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope2 email",map=kcmap&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And similarly add &lt;code>testuser2&lt;/code> to pg_ident, so both can log in:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># MAPNAME SYSTEM-USERNAME DATABASE-USERNAME
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kcmap testuser@example.com testuser
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kcmap testuser2@example.com testuser2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this new setup, we transformed the configuration problem into a permission issue where testuser2 can log in and testuser can not.&lt;/p>
&lt;p>The error message on the client side is unchanged, it still doesn’t say “permission denied” or “scope mismatch”, or anything like that.
This is debatable, but it is still mainly a task for administrators, and not the user:
somebody will have to investigate the permission setup on keycloak, and fix it, if testuser also needs access to the server.&lt;/p>
&lt;h3 id="unknown-user">Unknown user&lt;/h3>
&lt;p>Another common error source is a problem with the user mapping.
In our example we are using a pg_ident file with an email, but it would be similar with other configurations.&lt;/p>
&lt;p>Regardless of the setup, there are many reasons why we can’t properly look up a username:&lt;/p>
&lt;ul>
&lt;li>using an incorrect field for &lt;code>authn_field&lt;/code>&lt;/li>
&lt;li>missing an entry from &lt;code>pg_ident&lt;/code>&lt;/li>
&lt;li>having a typo in the name either in &lt;code>pg_ident&lt;/code> or in keycloak&lt;/li>
&lt;li>and so on&lt;/li>
&lt;/ul>
&lt;p>In all situations, the error message for this case won’t be generated in the validator, but in the PostgreSQL user mapping code instead.
For example, if you previously added &lt;code>testuser2&lt;/code> to the ident file, comment it out and try to log in with it again:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">LOG: no match in usermap "kcmap" for user "testuser" authenticated as "testuser2@example.com"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In an alternative configuration – which is not part of the sample keycloak configuration – it is possible to create a custom claim “postgres_username” on keycloak, and skip the map file completely.
In this situation, a mismatched username would result in a slightly different error message:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">LOG: provided user name (testuser) and authenticated user name (testuser2) do not match
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email""&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="connection-problems">Connection problems&lt;/h3>
&lt;p>While it usually isn’t a configuration or permission problem, it is possible that we have a network issue:
either a localized routing error, where the client can connect to the identity provider but the server can’t, or a situation where the identity provider / network crashed between obtaining the access token and verifying it on the server.&lt;/p>
&lt;p>The client executable has an access token and sends it to the server, which then has to validate it without being able to communicate with the identity provider.
This is another situation which is difficult to validate with &lt;code>psql&lt;/code>, but it is relatively easy with a custom client.&lt;/p>
&lt;p>Our OIDC validator has to connect to the identity provider for two reasons:&lt;/p>
&lt;ul>
&lt;li>One, to retrieve the discovery document which contains the URL of the JWKS endpoint – which stores the public keys of the issuer&lt;/li>
&lt;li>Two, to retrieve the public keys using that JWKS endpoint&lt;/li>
&lt;/ul>
&lt;p>The validator also follows HTTP Cache headers:
for example, if the server allows caching the keys for 4 days, the validator only retrieves them for the first attempt, and then keeps using them for that time.
After it passes, it connects to the server one more time, and if it again receives a 4 day window, it will keep using the keys for 4 more days.
This means that with a proper provider setup, the validator might not even notice a short service loss.&lt;/p>
&lt;p>Fortunately for our testing, but not so fortunately for production use, keycloak doesn’t support JWKS caching at all.&lt;/p>
&lt;p>An inaccessible OIDC server will result in logs similar to:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">WARNING: OAuth validation failed with exception: HTTP request failed: Could not connect to server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DETAIL: Connection matched file "&lt;datadir>/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where the exact error message depends on the situation – a timeout, internal server error, etc, all would result in slightly different error messages, while a timeout would also slow down the response time of the authentication attempt.&lt;/p>
&lt;h3 id="lets-run-without-errors">Let’s run without errors!&lt;/h3>
&lt;p>We hope these examples will be useful for everybody. To avoid errors, to diagnose problems, and to simply understand the security model and guarantees given by OAuth and validators.&lt;/p>
&lt;p>While this is not an all-inclusive list, as we can’t possibly cover every error scenario in a setup involving several components, it covers the most common scenarios, and should address all possible security problems.&lt;/p>
&lt;p>In our next blog post, we’ll focus on a practical, minimal development example:
while currently only the provided command line tools support OAuth, &lt;code>libpq&lt;/code> already has the infrastructure in it to implement custom OAuth logic, allowing users to integrate it into their applications - we’ll provide examples how it is doable.&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>OIDC</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2026/04/oidc-errors_hu_d443268dc7c62db9.jpg"/><media:content url="https://percona.community/blog/2026/04/oidc-errors_hu_53b05a0ce1e654cd.jpg" medium="image"/></item><item><title>pgBackRest is archived, what now?</title><link>https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/</link><guid>https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/</guid><pubDate>Tue, 28 Apr 2026 11:00:00 UTC</pubDate><description>pgBackRest is an open source backup and restore tool for PostgreSQL. It’s fair to say it’s one of the most popular options, widely used across the PostgreSQL ecosystem.</description><content:encoded>&lt;p>&lt;a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">pgBackRest&lt;/a> is an open source backup and restore tool for PostgreSQL. It’s fair to say it’s one of the most popular options, widely used across the PostgreSQL ecosystem.&lt;/p>
&lt;p>On 27 April 2026, pgBackRest maintainer David Steele announced on &lt;a href="https://www.linkedin.com/posts/davidsteele_after-a-lot-of-thought-i-have-decided-to-share-7454442611911655424-mVMS?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAD3qpgBKSXefFXDYJlyIbIdar9mZh-NYBw" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a> and in the &lt;a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">GitHub repository&lt;/a> that the project is becoming &lt;del>unmaintained&lt;/del> archived, starting with:&lt;/p>
&lt;blockquote>
&lt;p>TL;DR: pgBackRest is no longer being maintained. If you fork pgBackRest, please select a new name for your project.&lt;/p>&lt;/blockquote>
&lt;div style="width:70%; margin: auto;">
&lt;p>
&lt;figure>&lt;img width="1086" height="1216" sizes="(max-width: 1086px) 100vw, 1086px" srcset="https://percona.community/blog/2026/04/Jan-david-li_hu_45fd9471c2c7b283.webp 768w, https://percona.community/blog/2026/04/Jan-david-li_hu_78694a19b8e6ef30.webp 480w, https://percona.community/blog/2026/04/Jan-david-li_hu_95835d8f53db9d64.webp 1086w" src="https://percona.community/blog/2026/04/Jan-david-li_hu_95835d8f53db9d64.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/div>
&lt;p>If you’re reading this, you’re likely either affected or at least concerned. In this short write up I will do my best to calm your nerves, present short term as well as more long term ideas and options.&lt;/p>
&lt;h2 id="where-are-we-now---the-status-quo">Where are we now - the status quo&lt;/h2>
&lt;p>pgBackRest is a critical part of the PostgreSQL ecosystem, and nobody seriously expects it to simply disappear. What happens next is now up to the community.
One possible outcome is the emergence of multiple forks of pgBackRest. That raises the risk of fragmentation or, put bluntly, &lt;del>Clone&lt;/del> Fork Wars.&lt;/p>
&lt;div style="width:70%; margin: auto;">
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/Jan-forks_hu_a19ae9167bd96622.webp 768w, https://percona.community/blog/2026/04/Jan-forks_hu_c8d2b107cb2c0459.webp 480w, https://percona.community/blog/2026/04/Jan-forks_hu_4282ae308d071fad.webp 1400w" src="https://percona.community/blog/2026/04/Jan-forks_hu_4282ae308d071fad.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/div>
&lt;p>That said, there has already been a significant amount of discussion across the community, and one thing is clear:&lt;/p>
&lt;p>The PostgreSQL community acknowledges the problem and wants change.&lt;/p>
&lt;p>The challenge now is twofold:&lt;/p>
&lt;ul>
&lt;li>What can we do immediately to stabilize the situation?&lt;/li>
&lt;li>What direction should we take long term, without overcomplicating the short-term response?&lt;/li>
&lt;/ul>
&lt;h2 id="what-is-percona-planning">What is Percona planning&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/postgresql/14/solutions/backup-recovery.html#pgbackrest" target="_blank" rel="noopener noreferrer">Percona includes pgBackRest&lt;/a> in the &lt;a href="https://docs.percona.com/postgresql/14/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> as the recommended backup and restore solution. From our perspective, it remains the most mature, enterprise-ready and reliable option available. While alternatives like WAL-G or Barman are well regarded, our recommendation remains unchanged.&lt;/p>
&lt;p>To emphasize the message:&lt;/p>
&lt;blockquote>
&lt;p>the current situation does &lt;u>not&lt;/u> impact our recommendation.&lt;/p>&lt;/blockquote>
&lt;p>Percona will continue supporting pgBackRest. What that support looks like in terms of maintainership and collaboration with other organizations is still being actively discussed and will take time to solidify.&lt;/p>
&lt;p>The immediate priority is to avoid fragmentation. We want to ensure we don’t end up with multiple independent forks maintained in isolation.&lt;/p>
&lt;p>If you are a Percona customer, you remain fully supported. Please continue reporting issues through standard support channels. For our community users, we encourage you to use the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a>, we will do our best to help there.&lt;/p>
&lt;h2 id="the-power-of-open-source-community">The power of open source community&lt;/h2>
&lt;p>In an era where we often hear about companies reducing teams due to AI-driven cost optimization, it’s easy to forget that software is still built and maintained by people. This is especially true in open source.&lt;/p>
&lt;p>Two observations are worth calling out:&lt;/p>
&lt;ol>
&lt;li>People need sustainable funding, work cannot be assumed to be purely voluntary.&lt;/li>
&lt;li>A healthy open source project should not depend on a single company or individual.&lt;/li>
&lt;/ol>
&lt;p>The current situation is, to some extent, a result of the opposite model. pgBackRest development was largely driven by a single company and later single maintainer, &lt;a href="https://github.com/dwsteele" target="_blank" rel="noopener noreferrer">David Steele&lt;/a>, with sponsorship from Crunchy Data. While others have contributed (e.g.i &lt;a href="https://github.com/sfrost" target="_blank" rel="noopener noreferrer">Stephen Frost&lt;/a> and Stefan Fercot - &lt;a href="https://github.com/pgstef" target="_blank" rel="noopener noreferrer">pgstef&lt;/a>), and there was a wider team maintaining the project in the past, recently the project effectively relied on one primary maintainer.&lt;/p>
&lt;p>I think it’s fair to say we’ve seen a fair share &lt;a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">xkcd #2347&lt;/a> posted all over the internet over the course of last 24h. So here’s one more:&lt;/p>
&lt;div style="width:50%; margin: auto;">
&lt;p>
&lt;figure>&lt;img width="2236" height="2814" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/Jan-comic-neb_hu_e8dd1e77701cc398.webp 768w, https://percona.community/blog/2026/04/Jan-comic-neb_hu_4996149d15ecbaab.webp 480w, https://percona.community/blog/2026/04/Jan-comic-neb_hu_5371ac63ef109e55.webp 1400w" src="https://percona.community/blog/2026/04/Jan-comic-neb_hu_5371ac63ef109e55.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/div>
&lt;p>To avoid repeating this pattern, we (along with other vendors) are deliberately taking time before jumping into forks or immediate solutions. The goal is to find a sustainable, collaborative model rather than rushing into fragmentation.&lt;/p>
&lt;p>For comparison, it took the Linux Foundation 6 days to respond to the &lt;a href="https://github.com/redis/redis/pull/13157" target="_blank" rel="noopener noreferrer">Redis license change&lt;/a> by &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-launches-open-source-valkey-community" target="_blank" rel="noopener noreferrer">launching Valkey&lt;/a>. While this situation is different as there’s no license change in pgBackRest, it illustrates that meaningful coordination takes time.&lt;/p>
&lt;p>This is exactly where the open source community can demonstrate its strength.&lt;/p>
&lt;h2 id="what-are-the-long-term-options">What are the long term options?&lt;/h2>
&lt;p>This situation is particularly surprising to me personally, as I recently referenced David’s proposed transparent funding model in &lt;a href="https://www.postgresql.eu/events/pgconfde2026/" target="_blank" rel="noopener noreferrer">my talk&lt;/a> at &lt;a href="http://pgconf.de/" target="_blank" rel="noopener noreferrer">PGConf.DE&lt;/a> just last week.&lt;/p>
&lt;div style="width:30%; margin: auto;">
&lt;p>
&lt;figure>&lt;img width="688" height="2228" sizes="(max-width: 688px) 100vw, 688px" srcset="https://percona.community/blog/2026/04/Jan-david-money_hu_e31b45823d6093cc.webp 480w, https://percona.community/blog/2026/04/Jan-david-money_hu_2bf084722e534aaf.webp 688w" src="https://percona.community/blog/2026/04/Jan-david-money_hu_2bf084722e534aaf.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/div>
&lt;p>The idea, distributing funding across organizations that rely on the project, seemed like a promising path toward a more sustainable ecosystem. In hindsight, it appears that adoption of this model was either too slow or insufficient to support ongoing maintenance.&lt;/p>
&lt;p>Looking ahead, several long-term options are being discussed within the community:&lt;/p>
&lt;ul>
&lt;li>Establishing a foundation-backed project (similar to models used by &lt;a href="https://codeberg.org/" target="_blank" rel="noopener noreferrer">Codeberg&lt;/a> or the Linux Foundation)&lt;/li>
&lt;li>Creating a coordinated, multi-vendor stewardship model&lt;/li>
&lt;li>In more extreme scenarios, moving critical tooling closer to the PostgreSQL core ecosystem&lt;/li>
&lt;/ul>
&lt;p>These discussions are ongoing. If you’re attending &lt;a href="https://2026.pgconf.dev/" target="_blank" rel="noopener noreferrer">PGConf.Dev&lt;/a>, this will almost certainly be a major topic, especially in the extensions ecosystem track of community sessions in the &lt;a href="https://2026.pgconf.dev/schedule/tuesday" target="_blank" rel="noopener noreferrer">Canfor&lt;/a> room on Tuesday.&lt;/p>
&lt;h2 id="so-what-should-i-do-now">So what should I do now?&lt;/h2>
&lt;div style="width:70%; margin: auto;">
&lt;p>
&lt;figure>&lt;img width="1402" height="1122" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/04/Jan-what-now_hu_a09ee8a561b7aacb.webp 768w, https://percona.community/blog/2026/04/Jan-what-now_hu_5d146ea2a46dcffa.webp 480w, https://percona.community/blog/2026/04/Jan-what-now_hu_b262983f692a82be.webp 1400w" src="https://percona.community/blog/2026/04/Jan-what-now_hu_b262983f692a82be.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/div>
&lt;p>In short, nothing but wait. Yes, this means:&lt;/p>
&lt;blockquote>
&lt;p>Keep on using pgBackRest as you did!&lt;/p>&lt;/blockquote>
&lt;p>If your company is relying on pgBackRest, now is the time to engage. If you have capacity for this, please join the discussion (we’ve kicked off a thread on &lt;a href="https://forums.percona.com/t/pgbackrest-archival-discussion/40725?u=jan_wieremjewicz" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a> if you are looking for a place to join this topic)&lt;/p>
&lt;p>Rest assured that you can follow the updates from us, we will be messaging about the progress made in regards to establishing the future for pgBackRest.&lt;/p>
&lt;p>One thing to clear is: are there any immediate risks?&lt;/p>
&lt;blockquote>
&lt;p>Not new ones. There is the uncertainty that this is not a comfortable feeling. Rest assured that the longevity of the solution is not in jeopardy as we do have an obligation to our customer and user base to make sure the project is continued.&lt;/p>&lt;/blockquote></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pgBackRest</category><media:thumbnail url="https://percona.community/blog/2026/04/Jan-pgb-cover_hu_efdde22d280020de.jpg"/><media:content url="https://percona.community/blog/2026/04/Jan-pgb-cover_hu_1dd9d9d2a6404877.jpg" medium="image"/></item><item><title>Incremental backups in Percona Kubernetes Operator for MySQL</title><link>https://percona.community/blog/2026/04/17/incremental-backups-in-percona-kubernetes-operator-for-mysql/</link><guid>https://percona.community/blog/2026/04/17/incremental-backups-in-percona-kubernetes-operator-for-mysql/</guid><pubDate>Fri, 17 Apr 2026 10:00:00 UTC</pubDate><description>Starting with version 1.1.0, the Percona Kubernetes Operator for MySQL now supports incremental backups. This feature lets you backup only the changed data since the last backup, instead of copying your entire dataset each time. The result is dramatically smaller backup sizes, faster backup windows, and lower cloud storage costs.</description><content:encoded>&lt;p>Starting with version 1.1.0, the Percona Kubernetes Operator for MySQL now supports &lt;strong>incremental backups&lt;/strong>. This feature lets you backup only the changed data since the last backup, instead of copying your entire dataset each time. The result is dramatically smaller backup sizes, faster backup windows, and lower cloud storage costs.&lt;/p>
&lt;p>In this post, we’ll walk through how the feature works under the hood, how to configure it, and what to keep in mind when designing your backup strategy.&lt;/p>
&lt;h2 id="how-incremental-backups-work-in-percona-xtrabackup">How Incremental Backups Work in Percona XtraBackup&lt;/h2>
&lt;p>The foundation of this feature is &lt;a href="https://docs.percona.com/percona-xtrabackup/latest/" target="_blank" rel="noopener noreferrer">Percona XtraBackup (PXB)&lt;/a>, an open source backup tool for MySQL. PXB has supported incremental backups for a while, and the operator now brings that capability into the backup workflow.&lt;/p>
&lt;p>Every InnoDB data page carries a &lt;strong>Log Sequence Number (LSN)&lt;/strong>, which is a monotonically increasing counter that records when the page was last modified. When PXB takes an incremental backup, it scans data pages and copies only those with an LSN newer than a reference point. The output is a set of compact &lt;code>.delta&lt;/code> files instead of full tablespace copies.&lt;/p>
&lt;p>Each backup produces an &lt;code>xtrabackup_checkpoints&lt;/code> file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">backup_type = full-backuped
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">from_lsn = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">to_lsn = 7345291
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">last_lsn = 7345291&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Each incremental’s &lt;code>from_lsn&lt;/code> must equal the previous backup’s &lt;code>to_lsn&lt;/code>.&lt;/p>
&lt;h2 id="using-incremental-backups-with-the-operator">Using Incremental Backups with the Operator&lt;/h2>
&lt;h3 id="on-demand-incremental-backup">On-Demand Incremental Backup&lt;/h3>
&lt;p>First, you need a full backup to serve as the base:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQLBackup&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">weekly-full&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">s3-us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">full&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once the full backup succeeds, create an incremental:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQLBackup&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">daily-inc-1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">s3-us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">incremental&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The operator automatically discovers the latest succeeded full backup for the same cluster and storage, fetches its LSN, and creates an incremental backup. If you want to pin a specific base, simply use the &lt;code>incrementalBaseBackupName&lt;/code> field:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">incremental&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">incrementalBaseBackupName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">weekly-full&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="scheduled-backups-full--incremental">Scheduled Backups: Full + Incremental&lt;/h3>
&lt;p>The real power comes from combining full and incremental schedules. Here’s an example: weekly full backups with daily incrementals:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">weekly-full&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"0 0 * * 0"&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Sunday midnight&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">keep&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">s3-us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">full&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">daily-incremental&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"0 0 * * 1-6"&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Monday through Saturday&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">s3-us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">incremental&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>keep&lt;/code> rotation policy is chain-aware: it counts only full backups and automatically cascade-deletes all dependent incrementals when a full backup is rotated out.&lt;/p>
&lt;h3 id="restoring-from-an-incremental-backup">Restoring from an Incremental Backup&lt;/h3>
&lt;p>The &lt;code>PerconaServerMySQLRestore&lt;/code> custom resource allows you to restore from any point in an incremental, similar to restoring a full backup:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ps.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaServerMySQLRestore&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">restore-to-wednesday&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backupName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">daily-inc-3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The operator handles the complexity behind the scenes:&lt;/p>
&lt;ol>
&lt;li>Discovers the full chain by listing the cloud storage directory&lt;/li>
&lt;li>Downloads and prepares the base full backup&lt;/li>
&lt;li>Applies each incremental in sequence&lt;/li>
&lt;li>Applies the final incremental and rolls back uncommitted transactions&lt;/li>
&lt;li>Moves the prepared data back to the MySQL data directory&lt;/li>
&lt;/ol>
&lt;p>You don’t need to know which backup is the base or how many incrementals are in the chain, the operator figures it out.&lt;/p>
&lt;h2 id="how-it-works-under-the-hood">How It Works Under the Hood&lt;/h2>
&lt;h3 id="storage-layout">Storage Layout&lt;/h3>
&lt;p>The operator uses a specific directory convention to encode backup chains without any requiring any additional metadata:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">s3://bucket/prefix/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-cluster-2026-04-06-full/ # base full backup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-cluster-2026-04-06-full.incr/ # incremental chain directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-cluster-2026-04-07T000000-incr/ # Monday's incremental
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-cluster-2026-04-08T000000-incr/ # Tuesday's incremental
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-cluster-2026-04-09T000000-incr/ # Wednesday's incremental&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>.incr/&lt;/code> suffix creates a self-describing structure. Any cluster with access to the storage bucket can reconstruct the chain, making cross-cluster restores straightforward.&lt;/p>
&lt;h3 id="the-backup-flow">The Backup Flow&lt;/h3>
&lt;p>Here’s what happens when you create an incremental backup:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Resolve the base.&lt;/strong> The controller finds the latest succeeded full backup (or the one you specified) and annotates the incremental CR with &lt;code>percona.com/base-backup-name&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Fetch the LSN.&lt;/strong> The controller calls the xtrabackup sidecar’s &lt;code>/backup/checkpoint-info&lt;/code> endpoint. The sidecar downloads &lt;code>xtrabackup_checkpoints&lt;/code> from the previous backup via &lt;code>xbcloud get&lt;/code>, parses it, and returns the &lt;code>to_lsn&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Launch the backup job.&lt;/strong> A Kubernetes Job is created with the &lt;code>INCREMENTAL_LSN&lt;/code> environment variable set.&lt;/li>
&lt;li>&lt;strong>Stream to storage.&lt;/strong> The sidecar runs &lt;code>xtrabackup --backup --stream=xbstream --incremental-lsn=&lt;LSN>&lt;/code> and pipes the output through &lt;code>xbcloud put&lt;/code> to the cloud destination.&lt;/li>
&lt;/ol>
&lt;h3 id="chain-integrity-protection">Chain Integrity Protection&lt;/h3>
&lt;p>The operator enforces chain integrity at multiple levels:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Deletion guards:&lt;/strong> Only the latest incremental in a chain can be deleted. Attempting to delete a mid-chain backup is blocked using finalizers.&lt;/li>
&lt;li>&lt;strong>Cascade deletion:&lt;/strong> Deleting a full backup automatically removes all dependent incrementals, from newest to oldest.&lt;/li>
&lt;li>&lt;strong>Concurrent backup prevention:&lt;/strong> The controller uses a Lease-based mechanism to prevent multiple incremental backups from running at the same time.&lt;/li>
&lt;/ul>
&lt;h2 id="designing-your-backup-strategy">Designing Your Backup Strategy&lt;/h2>
&lt;h3 id="when-to-use-incremental-backups">When to Use Incremental Backups&lt;/h3>
&lt;p>Incremental backups shine when:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Your database is large but change rate is low.&lt;/strong> A 1 TB database with 2% daily change produces ~20 GB incremental backups instead of 1 TB full backups.&lt;/li>
&lt;li>&lt;strong>You need frequent backup points.&lt;/strong> Run hourly incrementals with minimal overhead.&lt;/li>
&lt;li>&lt;strong>Cloud storage costs matter.&lt;/strong> Example: with about &lt;strong>2%&lt;/strong> of the data changing each day, &lt;strong>one full backup&lt;/strong> plus &lt;strong>six daily incrementals&lt;/strong> needs roughly &lt;strong>one-fifth&lt;/strong> the space of keeping &lt;strong>six separate full backups&lt;/strong> over the same week.&lt;/li>
&lt;/ul>
&lt;h3 id="what-to-keep-in-mind">What to Keep in Mind&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>All chain members must use the same storage backend.&lt;/strong> You can’t mix S3 and GCS within a chain.&lt;/li>
&lt;li>&lt;strong>Chain integrity is critical.&lt;/strong> If a backup in the chain is corrupted, all subsequent incrementals in that chain become unrestorable. Regular full backups provide recovery checkpoints.&lt;/li>
&lt;li>&lt;strong>Restore time increases with chain length.&lt;/strong> Each incremental adds a prepare step. For very long chains, consider more frequent full backups.&lt;/li>
&lt;/ul>
&lt;h2 id="try-it-out">Try It Out&lt;/h2>
&lt;p>Incremental backups are available in Percona Operator for MySQL version 1.1.0 and later. If you’re already running the operator, upgrade your CRDs and add a &lt;code>type: incremental&lt;/code> schedule to your backup configuration.&lt;/p>
&lt;!-- TODO -->
&lt;ul>
&lt;li>&lt;a href="">Operator documentation: Backups&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-xtrabackup/latest/create-incremental-backup.html" target="_blank" rel="noopener noreferrer">Percona XtraBackup: Incremental backups&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mysql-operator" target="_blank" rel="noopener noreferrer">GitHub: percona/percona-server-mysql-operator&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Have questions or feedback? Join the conversation on the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a> or open an issue on GitHub. We’d love to hear how incremental backups are working for your MySQL-on-Kubernetes deployments.&lt;/p></content:encoded><author>Mayank Shah</author><category>Kubernetes</category><category>Community</category><category>Open Source</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2026/04/Mayank_hu_31954773b35ef288.jpg"/><media:content url="https://percona.community/blog/2026/04/Mayank_hu_daf6c5c035e7bf26.jpg" medium="image"/></item><item><title>Percona Bug Report: March 2026</title><link>https://percona.community/blog/2026/04/03/percona-bug-report-march-2026/</link><guid>https://percona.community/blog/2026/04/03/percona-bug-report-march-2026/</guid><pubDate>Fri, 03 Apr 2026 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://perconadev.atlassian.net/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs.&lt;/p>
&lt;hr>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10378" target="_blank" rel="noopener noreferrer">PS-10378&lt;/a>: In the MeCab plugin, BOOLEAN MODE full-text queries with a LIMIT clause do not behave as expected. Although the optimizer indicates that ranking should be skipped (Ft_hints: no_ranking), the query still performs full ranking and sorting before applying LIMIT, preventing the intended optimization and impacting performance.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.4.x&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46-37, 8.4.9-9, 9.7.0-0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10448" target="_blank" rel="noopener noreferrer">PS-10448&lt;/a>: Insert prepared statements fail on partitioned tables with timestamp-based partitions when the partition key uses a non-constant default (e.g., &lt;strong>CURRENT_TIMESTAMP&lt;/strong>). After initial execution, the statement remains bound to the original partition and fails with a partition mismatch error when data should go into a different partition.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.42-33, 8.0.43-34, 8.0.44-35, 8.4.7-7&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: &lt;a href="https://bugs.mysql.com/bug.php?id=119309" target="_blank" rel="noopener noreferrer">Bug #119309&lt;/a>&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Modify statements to explicitly use &lt;strong>NOW()&lt;/strong> (requires updating procedures)&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46-37, 8.4.9-9, 9.7.0-0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10481" target="_blank" rel="noopener noreferrer">PS-10481&lt;/a>: The range optimizer incorrectly falls back to a full table scan instead of using an index range scan for WHERE … IN() queries when values exceed column or prefix length on non-binary collations (e.g. utf8mb4_0900_ai_ci). A single truncated value in IN() can invalidate all valid ranges, forcing a full scan and degrading performance.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.4.x&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: &lt;a href="https://bugs.mysql.com/bug.php?id=118009" target="_blank" rel="noopener noreferrer">Bug #118009&lt;/a>&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not fixed yet&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10593" target="_blank" rel="noopener noreferrer">PS-10593&lt;/a>: The audit_log plugin can crash (segfault) during memcpy operations when configured with audit_log_strategy=PERFORMANCE, audit_log_policy=ALL, and buffering enabled. The issue can be reproduced under specific memory allocator setups (e.g., jemalloc) and also occurs with standard libc malloc, indicating instability in the plugin’s memory handling.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.34-26, 8.0.45-36, 8.4.7-7&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46-37, 8.4.9-9&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10990" target="_blank" rel="noopener noreferrer">PS-10990&lt;/a>: Server crashes (signal 11) in Item_cache::walk when executing queries that use JOIN with a subquery in an IN clause inside stored procedures. The issue occurs during query execution/privilege checking and is reproducible across MySQL and Percona Server 8.0.x versions.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.45-36&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: &lt;a href="https://bugs.mysql.com/bug.php?id=115885" target="_blank" rel="noopener noreferrer">Bug #115885&lt;/a>&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Execute the query outside the stored procedure&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not specified&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-10578" target="_blank" rel="noopener noreferrer">PS-10578&lt;/a>: The legacy audit_log plugin does not populate the DB field in audit records unless the session is started with the –database option. Even when a database is selected later using USE or referenced explicitly in queries, the DB field may remain empty.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.43-34, 8.0.45-36&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Use Audit Log Filter component (8.4) or audit log filter (8.0), where this issue is not reproducible&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not planned to be fixed&lt;/p>
&lt;hr>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4844" target="_blank" rel="noopener noreferrer">PXC-4844&lt;/a>: In PXC clusters under high load, inconsistency voting during DDL or DCL operations can trigger an internal deadlock, causing standby nodes to get stuck applying transactions and continuously request FC pause. Although voting completes successfully and no node is expelled, writes remain blocked in wsrep: replicating and certifying write set, effectively stalling the cluster until the affected node is restarted.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.42&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Restart the blocked standby node to restore cluster activity&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not fixed yet&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4799" target="_blank" rel="noopener noreferrer">PXC-4799&lt;/a>: In PXC clusters, when a backup lock (&lt;strong>LOCK INSTANCE FOR BACKUP&lt;/strong>) is active and a replicated DDL is pending, executing &lt;strong>FLUSH TABLES WITH READ LOCK&lt;/strong> on the same node can trigger a deadlock. This results in an inconsistency vote and causes the node to leave the cluster, disrupting backup operations.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.42, 8.0.43, 8.4.6&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Avoid running DDL operations during backup or use a single backup instance instead of parallel runs&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46, 8.4.9, 9.7.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4814" target="_blank" rel="noopener noreferrer">PXC-4814&lt;/a>: In PXC with &lt;strong>wsrep_OSU_method=‘RSU’&lt;/strong>, a failed DDL due to table name case mismatch (e.g., &lt;strong>OPTIMIZE TABLE&lt;/strong>) is incorrectly written to the binary log as a successful transaction (&lt;strong>error_code=0&lt;/strong>). This results in a GTID being generated for a failed operation, causing GTID inconsistencies across cluster nodes and in replication setups.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.33-25, 8.0.44, 8.4.6&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Validate table name case sensitivity before executing DDL in RSU mode&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.45, 8.4.8, 9.6.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4845" target="_blank" rel="noopener noreferrer">PXC-4845&lt;/a>: After an IST failure (e.g., due to network issues), a PXC node may remain running in an inconsistent state instead of restarting, causing the donor and other nodes to become unresponsive. The joiner node gets stuck during state transfer instead of failing cleanly, impacting overall cluster availability.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.42&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.45, 8.4.8, 9.6.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4849" target="_blank" rel="noopener noreferrer">PXC-4849&lt;/a>: A PXC node fails to start after successful SST when &lt;strong>read_only&lt;/strong> or &lt;strong>super_read_only&lt;/strong> is enabled and event scheduler objects exist on the donor. During initialization, the event scheduler fails to load, causing the node to abort, making it impossible to run read-only nodes with events defined in the cluster.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.44, 8.4.7&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Start the node without read_only, then enable it manually later, or remove events&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46, 8.4.9, 9.7.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4965" target="_blank" rel="noopener noreferrer">PXC-4965&lt;/a>: Passwords containing the &lt;code>'&lt;/code> character are incorrectly handled, causing syntax errors during replication (e.g., &lt;strong>SET PASSWORD&lt;/strong>) and triggering inconsistency voting that can force a node to leave the cluster.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.45, 8.4.7&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Avoid using &lt;code>'&lt;/code> character in passwords&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46, 8.4.8, 9.6.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-5198" target="_blank" rel="noopener noreferrer">PXC-5198&lt;/a>: Executing &lt;strong>SELECT … FOR UPDATE SKIP LOCKED&lt;/strong> can trigger InnoDB crashes with fatal errors (e.g., “Unknown error code 21: Skip locked records”) under concurrent transactional workloads. Instead of returning expected deadlock errors, the query causes mysqld to abort, impacting cluster stability.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.33-25, 8.0.35-27, 8.0.36-28&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Avoid using &lt;strong>SKIP LOCKED&lt;/strong> in &lt;strong>SELECT … FOR UPDATE&lt;/strong> queries&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.46, 8.4.8, 9.6.0&lt;/p>
&lt;hr>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3543" target="_blank" rel="noopener noreferrer">PXB-3543&lt;/a>: Incremental backups in XtraBackup can become significantly slower than full backups on instances with a very large number of small tables, due to excessive CPU usage in memset during incremental processing. This leads to severe performance degradation, with incremental backups taking hours compared to minutes for full backups.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.35-33, 8.0.35-34&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Use full backups instead of incremental backups&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.35-35, 8.4.0-6, 9.6.0-1&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3667" target="_blank" rel="noopener noreferrer">PXB-3667&lt;/a>: Installation of XtraBackup 8.4 fails on RHEL 9–based systems due to dependency conflicts between percona-xtrabackup-84, perl(DBD::mysql), and incompatible libmysqlclient versions. Percona Server 8.4 provides libmysqlclient.so.24, while required dependencies expect libmysqlclient.so.21, resulting in unresolved package installation errors.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.4.0-5&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not specified&lt;/p>
&lt;hr>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2519" target="_blank" rel="noopener noreferrer">PT-2519&lt;/a>: pt-query-digest fails when processing large, slow query logs, repeatedly throwing “Argument "" isn’t numeric” errors during the aggregate fingerprint stage. The tool retries multiple times but does not complete, resulting in stalled analysis and very slow progress.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.7.0, 3.7.1&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.3&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2511" target="_blank" rel="noopener noreferrer">PT-2511&lt;/a>: pt-summary incorrectly reports that sshd is not running due to an invalid awk expression used to detect the process. The script checks the wrong field in ps output, causing false negatives even when sshd is active.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.7.1&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.3&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2516" target="_blank" rel="noopener noreferrer">PT-2516&lt;/a>: pt-mongodb-index-check fails to detect duplicate indexes (e.g., &lt;code>{a:1}&lt;/code> and &lt;code>{a:1, b:1}&lt;/code>) and may produce no output, making it unclear whether the tool is functioning or connecting properly.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.7.1&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not specified&lt;/p>
&lt;hr>
&lt;h2 id="pmm-percona-monitoring-and-management">PMM [Percona Monitoring and Management]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14493" target="_blank" rel="noopener noreferrer">PMM-14493&lt;/a>: PMM fails to start when using Podman with the &lt;strong>–log-driver passthrough&lt;/strong> option due to an error opening /dev/stderr during Nginx initialization. This causes the container to exit with configuration test failure, while other log drivers work as expected.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.4.0, 3.4.1&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Use a different &lt;strong>–log-driver&lt;/strong> option such as none or journald&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.8.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14576" target="_blank" rel="noopener noreferrer">PMM-14576&lt;/a>: PMM Client reports “failed to get backup status” errors during MongoDB backups, marking them as failed in the UI even though backups are successfully completed by PBM. This leads to incorrect backup status reporting and confusion for users.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.5.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Avoid using PMM Backup Management (not ideal)&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.9.0, 3.X&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14594" target="_blank" rel="noopener noreferrer">PMM-14594&lt;/a>: PMM incorrectly reports compatible XtraBackup versions as incompatible with supported MySQL versions during backup validation. This causes backups to be blocked in PMM even when the installed XtraBackup version is the latest available and should be accepted.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.5.0, 3.6.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Use the xtrabackup command-line tool to take backups&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.9.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14852" target="_blank" rel="noopener noreferrer">PMM-14852&lt;/a>: Some panels in the MongoDB InMemory dashboard show no data because they incorrectly use WiredTiger-specific metrics. As a result, dashboards for InMemory storage engine deployments can display empty or misleading panels instead of relevant metrics.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.2.0, 3.6.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.8.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14906" target="_blank" rel="noopener noreferrer">PMM-14906&lt;/a>: The postgres_exporter generates excessive &lt;strong>SELECT version()&lt;/strong> queries (~4500/hour) after upgrading to PMM 3.6.0, flooding PostgreSQL logs and increasing unnecessary query load, causing log spam and disk growth.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.6.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.8.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-14958" target="_blank" rel="noopener noreferrer">PMM-14958&lt;/a>: mysqld_exporter continues to generate duplicate metric collection errors with GTID and parallel replication enabled, even in PMM 3.6.0. These repeated errors (e.g., &lt;strong>mysql_perf_schema_replication_group_worker_transport_time_seconds&lt;/strong>) lead to continuous log spam, causing rapid log growth (up to ~10GB/hour), disk space exhaustion, and increased noise that makes it difficult to identify real issues.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.6.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.1&lt;/p>
&lt;hr>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-737" target="_blank" rel="noopener noreferrer">K8SPG-737&lt;/a>: In PostgreSQL Kubernetes deployments, the node_exporter in the PMM client sidecar cannot access the datadir mountpoint because it is not exposed via /proc, preventing collection of datadir-related metrics. This results in incomplete monitoring data for PostgreSQL pods.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.9.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.10.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1737" target="_blank" rel="noopener noreferrer">K8SPXC-1737&lt;/a>: The PXC Operator crashes during reconciliation in CompareMySQLVersion when the cluster status lacks a MySQL version value. An empty version field causes a panic (“Malformed version”), preventing proper cluster reconciliation and replication setup.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.18.0, 1.19.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Create the cluster before configuring replication or manually patch the CR status to include the missing version value, for example:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl patch pxc &lt;cluster-name> \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --type=merge \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --subresource=status \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --patch '
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">status:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pxc:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> version: "8.0.42-33.1"'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Fixed/Planned Version/s:&lt;/strong> 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1843" target="_blank" rel="noopener noreferrer">K8SPXC-1843&lt;/a>: Backups can get stuck in a Running state if the Joiner/Garbd disconnects from the Donor (e.g., due to sst-idle-timeout). Even after the SST process fails and the donor leaves the cluster, the backup process (e.g., xbcloud put) continues indefinitely without timing out, preventing backup completion.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1831" target="_blank" rel="noopener noreferrer">K8SPXC-1831&lt;/a>: When using mysqlAllocator=jemalloc on ARM images, the operator attempts to preload /usr/lib64/libjemalloc.so.1, but only libjemalloc.so.2 is available. This results in preload errors and prevents proper use of the jemalloc allocator.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1830" target="_blank" rel="noopener noreferrer">K8SPXC-1830&lt;/a>: ProxySQL monitoring fails in PMM when using caching_sha2_password, causing proxysql_exporter to fail authentication with errors like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Error opening connection to ProxySQL:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unexpected resp from server for caching_sha2_password, perform full authentication&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This occurs because ProxySQL does not support the required RSA-based full authentication, breaking PMM monitoring integration.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Use &lt;code>mysql_native_password&lt;/code>&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1617" target="_blank" rel="noopener noreferrer">K8SPSMDB-1617&lt;/a>: Scheduled backups can be triggered even when the MongoDB cluster is not ready (e.g., in initializing state) and without the required safety flags. This leads to failed backup attempts and inconsistent backup behaviour.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.22.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not specified&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1524" target="_blank" rel="noopener noreferrer">K8SPSMDB-1524&lt;/a>: The PBM agent continuously triggers resync storage operations, causing backup processes to stall or remain in pending/unknown states. Logs show repeated resync commands being executed without completion, leading to unstable backup behaviour.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.21.1&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.22.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-939" target="_blank" rel="noopener noreferrer">K8SPG-939&lt;/a>: Patroni does not propagate labels defined in the PostgreSQL Operator CR, causing failures in environments with strict label policies. As a result, Kubernetes rejects resource creation (e.g., Services) due to missing mandatory labels, preventing cluster reconciliation.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.8.2&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.9.0&lt;/p>
&lt;hr>
&lt;h2 id="pbm-percona-backup-for-mongodb">PBM [Percona Backup for MongoDB]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1683" target="_blank" rel="noopener noreferrer">PBM-1683&lt;/a>: The size_uncompressed_h field in pbm describe-backup reports incorrect (inflated) sizes for non-base incremental backups, showing significantly larger values than the actual data size and leading to misleading backup size reporting.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.10.0, 2.11.0, 2.12.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.14.0&lt;/p>
&lt;hr>
&lt;h2 id="psmdb-percona-server-for-mongodb">PSMDB [Percona Server for MongoDB]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PSMDB-1915" target="_blank" rel="noopener noreferrer">PSMDB-1915&lt;/a>: Newer PSMDB packages fail to install or upgrade on RHEL 9.4 due to a dependency on OpenSSL 3.4, which is not available in that OS version. This breaks upgrades (e.g., from 6.0.25 to 6.0.27) and affects multiple major versions.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 6.0.27-21, 7.0.28-15, 8.0.17-6&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 6.0.27-21, 7.0.28-15, 8.0.17-6&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PSMDB-1998" target="_blank" rel="noopener noreferrer">PSMDB-1998&lt;/a>: LDAP authentication can hang indefinitely when the LDAP server is unreachable due to missing timeout handling. This leads to continuously accumulating connections, eventually exhausting file descriptors and causing service disruption or crashes.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 7.0.16-10, 7.0.30-16&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: No workaround available&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 7.0.31-17, 8.0.20-8&lt;/p>
&lt;hr>
&lt;h2 id="percona-distribution-for-mysql-orchestrator">Percona Distribution for MySQL [Orchestrator]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/DISTMYSQL-584" target="_blank" rel="noopener noreferrer">DISTMYSQL-584&lt;/a>: Orchestrator loses SSL-related settings such as SOURCE_SSL_CA and SOURCE_SSL_VERIFY_SERVER_CERT during failover when issuing CHANGE REPLICATION SOURCE, causing replication to run without required security configurations and potentially violating compliance requirements.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.4.7&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: Not specified&lt;/p>
&lt;hr>
&lt;h2 id="pcsm-percona-clustersync-for-mongodb">PCSM [Percona ClusterSync for MongoDB]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PCSM-294" target="_blank" rel="noopener noreferrer">PCSM-294&lt;/a>: PCSM replication can crash during change replication due to flawed conflict detection and unbatched pipeline generation. This results in oversized aggregation pipelines, memory exhaustion, or invalid $slice operations, causing replication to fail with errors such as stage limit exceeded, buffer limits, or invalid arguments.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 0.7.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: 0.8.0&lt;/p>
&lt;hr>
&lt;h2 id="pg_tde-percona-transparent-data-encryption-for-postgresql">PG_TDE [Percona Transparent Data Encryption for PostgreSQL]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PG-2125" target="_blank" rel="noopener noreferrer">PG-2125&lt;/a>: pg_tde fails to create/register symmetric keys when using HashiCorp KMIP, returning errors from the KMIP server during key registration. This prevents key setup and blocks encryption workflows for users relying on KMIP integration.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: pg_tde 2.1.0&lt;br>
&lt;strong>Upstream Bug&lt;/strong>: Not applicable&lt;br>
&lt;strong>Workaround/Fix&lt;/strong>: Not specified&lt;br>
&lt;strong>Fixed/Planned Version/s&lt;/strong>: pg_tde NEXT&lt;/p>
&lt;hr>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;p>&lt;a href="https://jira.percona.com" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p></content:encoded><author>Aaditya Dubey</author><category>Percona Server/MySQL</category><category>Percona XtraDB Cluster</category><category>Percona XtraBackup</category><category>Percona Toolkit</category><category>PMM</category><category>Kubernetes Operator</category><category>PBM</category><category>PSMDB</category><category>Percona Distribution for MySQL</category><category>Orchestrator</category><category>PCSM</category><category>PG_TDE</category><media:thumbnail url="https://percona.community/blog/2026/04/BugReportMarch2026_hu_a1e1c87ec055cccb.jpg"/><media:content url="https://percona.community/blog/2026/04/BugReportMarch2026_hu_d15272a9622ff37.jpg" medium="image"/></item><item><title>InnoDB Buffer Pool Tuning: From Rule-of-Thumb to Real Signals</title><link>https://percona.community/blog/2026/04/02/innodb-buffer-pool-tuning-from-rule-of-thumb-to-real-signals/</link><guid>https://percona.community/blog/2026/04/02/innodb-buffer-pool-tuning-from-rule-of-thumb-to-real-signals/</guid><pubDate>Thu, 02 Apr 2026 00:00:00 UTC</pubDate><description>Introduction Many MySQL setups begin life with a familiar incantation:</description><content:encoded>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Many MySQL setups begin life with a familiar incantation:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_size = 70% of RAM&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>…and then nothing changes.&lt;/p>
&lt;p>That’s not tuning. That’s a starting guess.&lt;/p>
&lt;p>Real tuning starts when the workload pushes back.&lt;/p>
&lt;hr>
&lt;h2 id="visual-overview">Visual Overview&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1024" height="1536" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2026/04/innodb_buffer_pool_diagram_hu_4e6f91619b662f84.webp 768w, https://percona.community/blog/2026/04/innodb_buffer_pool_diagram_hu_2d554f2da004bd40.webp 480w, https://percona.community/blog/2026/04/innodb_buffer_pool_diagram_hu_fccfce324d38a928.webp 1024w" src="https://percona.community/blog/2026/04/innodb_buffer_pool_diagram_hu_fccfce324d38a928.webp" alt="InnoDB Buffer Pool Diagram" loading="lazy" />&lt;/figure>&lt;/p>
&lt;hr>
&lt;p>The InnoDB buffer pool is where database performance is quietly decided. It determines whether your workload hums along in memory or drags itself across disk. If you’re not actively observing and tuning it, you’re leaving performance on the table.&lt;/p>
&lt;p>This guide walks through how to monitor, understand, and tune the buffer pool using real signals instead of guesswork.&lt;/p>
&lt;hr>
&lt;h2 id="what-the-buffer-pool-really-is">What the Buffer Pool Really Is&lt;/h2>
&lt;p>The buffer pool isn’t just “memory for MySQL.” It’s a living system under constant pressure:&lt;/p>
&lt;ul>
&lt;li>A cache of data and indexes&lt;/li>
&lt;li>A write staging area (dirty pages)&lt;/li>
&lt;li>A contention zone between reads, writes, and eviction&lt;/li>
&lt;/ul>
&lt;p>Think of it as your database’s working memory. If your working set fits, queries glide. If it doesn’t, pages are constantly evicted and reloaded, introducing latency that rarely announces itself clearly.&lt;/p>
&lt;hr>
&lt;h2 id="a-simple-mental-model">A Simple Mental Model&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> +---------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | Buffer Pool |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> |---------------------------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Reads ---> | Cached Pages |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writes ---> | Dirty Pages (pending IO) |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Eviction -> | LRU / Free List |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +---------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> v
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Disk (slow)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Three forces are always competing:&lt;/p>
&lt;ul>
&lt;li>Reads want hot data in memory&lt;/li>
&lt;li>Writes generate dirty pages&lt;/li>
&lt;li>Eviction makes room under pressure&lt;/li>
&lt;/ul>
&lt;p>Your job is to keep this system balanced.&lt;/p>
&lt;hr>
&lt;h2 id="how-to-monitor-the-buffer-pool">How to Monitor the Buffer Pool&lt;/h2>
&lt;h3 id="option-1-quick-snapshot">Option 1: Quick Snapshot&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENGINE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">INNODB&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Useful for human inspection. Look for:&lt;/p>
&lt;ul>
&lt;li>Buffer pool size&lt;/li>
&lt;li>Free buffers&lt;/li>
&lt;li>Database pages&lt;/li>
&lt;li>Modified (dirty) pages&lt;/li>
&lt;li>Page read/write rates&lt;/li>
&lt;/ul>
&lt;p>Great for debugging. Not ideal for automation.&lt;/p>
&lt;hr>
&lt;h3 id="option-2-structured-metrics-recommended">Option 2: Structured Metrics (Recommended)&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">pool_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">free_buffers&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">database_pages&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">modified_database_pages&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Key fields:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>free_buffers&lt;/code> → Available pages (breathing room)&lt;/li>
&lt;li>&lt;code>database_pages&lt;/code> → Pages holding data&lt;/li>
&lt;li>&lt;code>modified_database_pages&lt;/code> → Dirty pages waiting to flush&lt;/li>
&lt;/ul>
&lt;p>Great for automation.&lt;/p>
&lt;hr>
&lt;h2 id="the-5-signals-that-actually-matter">The 5 Signals That Actually Matter&lt;/h2>
&lt;h3 id="1-buffer-pool-hit-ratio-handle-with-care">1. Buffer Pool Hit Ratio (Handle With Care)&lt;/h3>
&lt;p>Yes, it’s widely used. No, it’s not enough.&lt;/p>
&lt;p>A high hit ratio does not mean your system is healthy. It does not capture:&lt;/p>
&lt;ul>
&lt;li>Page churn&lt;/li>
&lt;li>Eviction pressure&lt;/li>
&lt;li>Access patterns&lt;/li>
&lt;/ul>
&lt;p>You can have a 99% hit ratio and still be IO-bound.&lt;/p>
&lt;p>Use it as a sanity check, not a decision-maker.&lt;/p>
&lt;hr>
&lt;h3 id="2-free-buffers">2. Free Buffers&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">free_buffers&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">free_buffers&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Interpretation:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Near zero during steady load → normal&lt;/li>
&lt;li>Near zero + rising disk reads → pressure&lt;/li>
&lt;li>Near zero while mostly idle → suspicious (possible misread or config issue)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="3-dirty-page-percentage">3. Dirty Page Percentage&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">modified_database_pages&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">database_pages&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dirty_pct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Interpretation (context matters):&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>0–5% → Very clean&lt;/li>
&lt;li>5–20% → Typical&lt;/li>
&lt;li>20–30%+ → Potential flushing lag&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="4-disk-read-pressure-critical-signal">4. Disk Read Pressure (Critical Signal)&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_buffer_pool_reads'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Take two samples 60s apart and compare&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Track the rate of change (reads/sec), not the absolute value.&lt;/p>
&lt;p>&lt;strong>Interpretation:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Rising reads → Working set does not fit in memory&lt;/li>
&lt;li>Flat reads → Memory is absorbing the workload&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="5-read-ahead--eviction-pressure">5. Read Ahead / Eviction Pressure&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_buffer_pool_read_ahead%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_buffer_pool_pages_evicted'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_buffer_pool_reads'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Interpretation:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Efficient read-ahead:
&lt;ul>
&lt;li>read_ahead increases&lt;/li>
&lt;li>read_ahead_evicted remains low&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Inefficient read-ahead (wasted IO):
&lt;ul>
&lt;li>High read_ahead_evicted / read_ahead&lt;/li>
&lt;li>Indicates access patterns defeating prefetching&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Buffer pool churn:
&lt;ul>
&lt;li>pages_evicted rising&lt;/li>
&lt;li>buffer_pool_reads rising&lt;/li>
&lt;li>Indicates pages are evicted and re-read from disk&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Healthy vs unhealthy eviction:
&lt;ul>
&lt;li>High evictions + stable reads → normal turnover&lt;/li>
&lt;li>High evictions + rising reads → memory pressure&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Focus on rates of change over time, not absolute values.&lt;/p>
&lt;hr>
&lt;h2 id="detecting-thrashing">Detecting Thrashing&lt;/h2>
&lt;p>Thrashing is when the buffer pool constantly evicts and reloads pages.&lt;/p>
&lt;h3 id="classic-symptoms">Classic Symptoms&lt;/h3>
&lt;ul>
&lt;li>Low or zero free buffers&lt;/li>
&lt;li>Increasing disk reads&lt;/li>
&lt;li>Stable (but misleading) hit ratio&lt;/li>
&lt;li>Spiky query latency&lt;/li>
&lt;/ul>
&lt;h3 id="visualizing-thrash">Visualizing Thrash&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Time --->
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Memory: [FULL][FULL][FULL][FULL]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Reads: ↑ ↑↑ ↑↑↑ ↑↑↑↑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Latency: - ^ ^^ ^^^
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evictions: ↑ ↑↑ ↑↑↑ ↑↑↑↑&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you see this pattern, your working set does not fit in memory.&lt;/p>
&lt;hr>
&lt;h2 id="tuning-the-buffer-pool">Tuning the Buffer Pool&lt;/h2>
&lt;h3 id="step-1-size-it-intentionally">Step 1: Size It Intentionally&lt;/h3>
&lt;p>Instead of blindly assigning 70% of RAM:&lt;/p>
&lt;ul>
&lt;li>Observe working set behavior&lt;/li>
&lt;li>Monitor free buffers and reads&lt;/li>
&lt;li>Increase gradually&lt;/li>
&lt;/ul>
&lt;p>Avoid starving the OS or filesystem cache.&lt;/p>
&lt;hr>
&lt;h3 id="step-2-tune-flushing-behavior">Step 2: Tune Flushing Behavior&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">innodb_max_dirty_pages_pct = 75
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_io_capacity = 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_io_capacity_max = 2000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>Sustained IO spikes → increase innodb_io_capacity&lt;/li>
&lt;li>Dirty pages climbing → flushing lag&lt;/li>
&lt;li>Sudden stalls → checkpoint pressure&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>What they control:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>innodb_io_capacity&lt;/code> → Expected steady-state IO throughput&lt;/li>
&lt;li>&lt;code>innodb_io_capacity_max&lt;/code> → Burst flushing capacity&lt;/li>
&lt;li>&lt;code>innodb_max_dirty_pages_pct&lt;/code> → Threshold for aggressive flushing&lt;/li>
&lt;/ul>
&lt;p>⚠️ These values should reflect real hardware capability.&lt;/p>
&lt;hr>
&lt;h3 id="step-3-buffer-pool-instancesreduce-contention">Step 3: Buffer Pool Instances:Reduce Contention&lt;/h3>
&lt;p>A practical, battle-tested guideline:&lt;/p>
&lt;p>Use 1 instance per ~1GB of buffer pool, up to a reasonable limit.&lt;/p>
&lt;p>Buffer Pool Instances: Reducing Contention&lt;/p>
&lt;p>The buffer pool can be split into multiple instances, each managing its own internal structures. This helps reduce contention under high concurrency.&lt;/p>
&lt;p>Without this, all threads compete for the same buffer pool internals. With multiple instances, that load is distributed.&lt;/p>
&lt;hr>
&lt;h3 id="when-it-matters">When It Matters&lt;/h3>
&lt;p>Buffer pool instances only help when contention exists. You’ll see benefits if your system has:&lt;/p>
&lt;ul>
&lt;li>High concurrency (many active threads)&lt;/li>
&lt;li>CPU-bound workloads&lt;/li>
&lt;li>Mutex contention in InnoDB&lt;/li>
&lt;/ul>
&lt;p>If your workload is primarily IO-bound, this setting will have little impact.&lt;/p>
&lt;hr>
&lt;h3 id="sizing-guidelines">Sizing Guidelines&lt;/h3>
&lt;p>General guidance:&lt;/p>
&lt;ul>
&lt;li>&lt; 1GB buffer pool → 1 instance&lt;/li>
&lt;li>1GB–8GB → 2–4 instances&lt;/li>
&lt;li>8GB–64GB → 4–8 instances&lt;/li>
&lt;li>64GB+ → 8–16 instances&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="keep-instances-large-enough">Keep Instances Large Enough&lt;/h3>
&lt;p>Each instance needs enough memory to function efficiently.&lt;/p>
&lt;p>Avoid going below ~1GB per instance.&lt;/p>
&lt;p>If instances are too small:&lt;/p>
&lt;ul>
&lt;li>LRU efficiency drops&lt;/li>
&lt;li>Eviction becomes more aggressive&lt;/li>
&lt;li>Cache locality suffers&lt;/li>
&lt;/ul>
&lt;p>Example&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">innodb_buffer_pool_size&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">32&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">innodb_buffer_pool_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">8&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This gives ~4GB per instance, which is well-balanced.&lt;/p>
&lt;hr>
&lt;h3 id="common-mistakes">Common Mistakes&lt;/h3>
&lt;ul>
&lt;li>Increasing instances without evidence of contention&lt;/li>
&lt;li>Matching instance count to CPU cores&lt;/li>
&lt;li>Using many instances with a small buffer pool&lt;/li>
&lt;li>Expecting this to fix IO bottlenecks&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="step-4-understand-resizing-behavior">Step 4: Understand Resizing Behavior&lt;/h3>
&lt;p>Buffer pool resizing is online in modern MySQL versions, but:&lt;/p>
&lt;ul>
&lt;li>It happens in chunks&lt;/li>
&lt;li>Controlled by &lt;code>innodb_buffer_pool_chunk_size&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="real-world-scenarios">Real-World Scenarios&lt;/h2>
&lt;h3 id="scenario-1-everything-looks-fine-but-its-slow">Scenario 1: “Everything Looks Fine… But It’s Slow”&lt;/h3>
&lt;ul>
&lt;li>High hit ratio&lt;/li>
&lt;li>Low free buffers&lt;/li>
&lt;li>Rising disk reads&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cause:&lt;/strong> Working set barely fits&lt;/p>
&lt;p>&lt;strong>Fix:&lt;/strong> Increase buffer pool size gradually&lt;/p>
&lt;p>If increasing the buffer pool size does not reduce disk reads, the problem is not memory.&lt;/p>
&lt;hr>
&lt;h3 id="scenario-2-write-heavy-workload">Scenario 2: Write-Heavy Workload&lt;/h3>
&lt;ul>
&lt;li>Dirty pages increasing&lt;/li>
&lt;li>Periodic IO spikes&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cause:&lt;/strong> Flushing cannot keep up&lt;/p>
&lt;p>&lt;strong>Fix:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Increase &lt;code>innodb_io_capacity&lt;/code>&lt;/li>
&lt;li>Adjust dirty page thresholds&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="scenario-3-sudden-latency-spikes">Scenario 3: Sudden Latency Spikes&lt;/h3>
&lt;ul>
&lt;li>Sharp performance drops&lt;/li>
&lt;li>Disk activity surges&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cause:&lt;/strong> Checkpoint pressure&lt;/p>
&lt;p>&lt;strong>Fix:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Improve IO capacity tuning&lt;/li>
&lt;li>Reduce dirty page buildup&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="practical-monitoring-queries">Practical Monitoring Queries&lt;/h2>
&lt;h3 id="buffer-pool-usage-mb">Buffer Pool Usage (MB)&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">database_pages&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mb_used&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Assumes default 16KB page size (innodb_page_size).&lt;/p>
&lt;h3 id="dirty-page-percentage">Dirty Page Percentage&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">modified_database_pages&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">database_pages&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dirty_pct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="free-buffer-check">Free Buffer Check&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">free_buffers&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">free_buffers&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">information_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">INNODB_BUFFER_POOL_STATS&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h2 id="common-mistakes-1">Common Mistakes&lt;/h2>
&lt;ul>
&lt;li>Treating 70% as a rule instead of a starting point&lt;/li>
&lt;li>Blindly trusting hit ratio&lt;/li>
&lt;li>Ignoring disk read trends&lt;/li>
&lt;li>Oversizing and starving the OS&lt;/li>
&lt;li>Not tuning IO capacity&lt;/li>
&lt;li>Leaving defaults in write-heavy systems&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="quick-checklist">Quick Checklist&lt;/h2>
&lt;p>If you remember nothing else:&lt;/p>
&lt;ul>
&lt;li>Reads increasing? → working set too big&lt;/li>
&lt;li>Free buffers always ~0? → pressure&lt;/li>
&lt;li>Dirty pages high? → flushing lag&lt;/li>
&lt;li>Latency spiking? → checkpoint or IO saturation&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>The InnoDB buffer pool doesn’t fail loudly. It degrades quietly until your disk becomes the bottleneck.&lt;/p>
&lt;p>By the time you notice, you’re debugging latency instead of preventing it.&lt;/p>
&lt;p>Monitor the right signals, and you’ll see problems forming before users do.&lt;/p>
&lt;p>That’s the difference between reacting to performance… and controlling it.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Percona</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>innodb bufferpool</category><category>tuning</category><media:thumbnail url="https://percona.community/blog/2026/04/bufferpool-tuning_hu_7481b328ae6e02da.jpg"/><media:content url="https://percona.community/blog/2026/04/bufferpool-tuning_hu_1ac592e54c20c4ce.jpg" medium="image"/></item><item><title>Running pgBackRest with pg_tde: A Practical Percona Walkthrough</title><link>https://percona.community/blog/2026/03/10/running-pgbackrest-with-pg_tde-a-practical-percona-walkthrough/</link><guid>https://percona.community/blog/2026/03/10/running-pgbackrest-with-pg_tde-a-practical-percona-walkthrough/</guid><pubDate>Tue, 10 Mar 2026 00:00:00 UTC</pubDate><description>Not every PostgreSQL installation requires encryption at rest. However, for organizations mandating strict data protection and privacy standards, it is often non-negotiable. When security policies are this rigorous, you need a strategy that protects your data without sacrificing recoverability.</description><content:encoded>&lt;p>Not every PostgreSQL installation requires encryption at rest. However, for organizations mandating strict data protection and privacy standards, it is often non-negotiable. When security policies are this rigorous, you need a strategy that protects your data without sacrificing recoverability.&lt;/p>
&lt;p>While Transparent Data Encryption (TDE) successfully locks down your data at rest, it raises a critical operational question: will your backup and restore workflow continue to work correctly with encrypted data? When deploying Transparent Data Encryption, it is essential to validate that pgBackRest can reliably back up and restore encrypted clusters.&lt;/p>
&lt;p>This post walks through a complete, step-by-step configuration of pg_tde with pgBackRest on Debian/Ubuntu. We will explore how to optimize your backup performance, secure your repository, and most importantly verify that your encrypted backup restores work exactly as expected.&lt;/p>
&lt;h2 id="what-is-pg_tde">What is pg_tde?&lt;/h2>
&lt;p>Percona’s solution for transparent data encryption (&lt;a href="https://docs.percona.com/pg-tde/index.html" target="_blank" rel="noopener noreferrer">pg_tde&lt;/a>) is an open source, community driven extension that provides Transparent Data Encryption (TDE) for PostgreSQL. This extension allows data to be encrypted at the storage level without affecting application behavior.&lt;/p>
&lt;p>Unlike full disk encryption, which exposes data once the system boots, TDE ensures that the actual database files remain encrypted at the file system level. This protects your data, dumps, and backups even if the operating system is compromised. Currently, it is bundled with Percona Server for PostgreSQL and available in Percona Distribution for PostgreSQL 17+.&lt;/p>
&lt;p>Recent Percona releases make this combination more practical than ever. With WAL encryption now ready for production use, we need a backup strategy that respects data security. In this walkthrough, we will demonstrate how to pair it with pgBackRest to ensure fully recoverable, encrypted backups.&lt;/p>
&lt;h2 id="the-use-case">The Use Case&lt;/h2>
&lt;p>Imagine a team that wants strong security controls without changing application code. They need:&lt;/p>
&lt;ul>
&lt;li>Data files encrypted at rest (tables and WAL)&lt;/li>
&lt;li>Backups that are consistent, verifiable, and restorable&lt;/li>
&lt;li>A setup that is easy to automate and explain&lt;/li>
&lt;/ul>
&lt;p>Percona Distribution for PostgreSQL plus pg_tde and pgBackRest closes the gaps where needed: pg_tde takes care of encryption, pgBackRest provides flexible backup/restore capabilities.&lt;/p>
&lt;h2 id="a-quick-note-on-compatibility">A Quick Note on Compatibility&lt;/h2>
&lt;p>At this moment pg_tde cannot be yet used with Community PostgreSQL as pg_tde relies on specific hooks in the PostgreSQL core. Percona Server for PostgreSQL includes these necessary core modifications, which is why we validate this setup using the Percona Distribution for PostgreSQL.&lt;/p>
&lt;p>While pgBackRest is fully capable of managing TDE enabled clusters, there are specific constraints you must respect to ensure data safety:&lt;/p>
&lt;ul>
&lt;li>No Asynchronous Archiving: pgBackRest asynchronous archiving is not supported with encrypted WALs. You must configure your archive command to handle WALs synchronously.&lt;/li>
&lt;li>Restore Wrappers: Standard restore commands will not work for encrypted WALs. You must use the pg_tde_restore_encrypt utility to wrap your restore process.&lt;/li>
&lt;/ul>
&lt;p>This guide currently focuses on pgBackRest because it is the backup tool that has been tested and validated with pg_tde by the pg_tde community at this time.
Other backup tools may also be viable and we are open to collaborating with other tool maintainers and their communities on a shared effort to validate and support pg_tde.&lt;/p>
&lt;h2 id="what-you-will-build">What You Will Build&lt;/h2>
&lt;p>By the end of this post you will have:&lt;/p>
&lt;ul>
&lt;li>Percona Distribution for PostgreSQL installed with pg_tde and pgBackRest&lt;/li>
&lt;li>A key directory and key providers created&lt;/li>
&lt;li>pg_tde enabled in PostgreSQL&lt;/li>
&lt;li>Encrypt tables and indexes with pg_tde (&lt;a href="https://docs.percona.com/pg-tde/test.html" target="_blank" rel="noopener noreferrer">docs&lt;/a>)&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/pg-tde/wal-encryption.html" target="_blank" rel="noopener noreferrer">WAL encryption&lt;/a> enabled&lt;/li>
&lt;li>A pgBackRest stanza configured and a full backup completed&lt;/li>
&lt;li>Verification that encrypted data is not readable on disk&lt;/li>
&lt;/ul>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>A host (or VM) on Debian/Ubuntu&lt;/li>
&lt;li>Network access to Percona repositories&lt;/li>
&lt;li>Root/Sudo: You need sudo access for installing packages and editing system configuration files in &lt;code>/etc&lt;/code>&lt;/li>
&lt;li>Postgres User: All database commands (psql, pgbackrest) run as the postgres system user&lt;/li>
&lt;li>Postgres user is in the sudoer list to run sudo commands&lt;/li>
&lt;/ul>
&lt;h2 id="step-1-install-percona-packages">Step 1: Install Percona Packages&lt;/h2>
&lt;p>We begin by installing the Percona release repository and enabling the correct PostgreSQL distribution. See the &lt;a href="https://docs.percona.com/postgresql/18/installing.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL installation guide&lt;/a> for full details.&lt;/p>
&lt;h3 id="debian--ubuntu">Debian / Ubuntu&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install repo helper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install -y wget gnupg2 lsb-release curl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dpkg -i percona-release_latest.generic_all.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Enable the repository for the major version selected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo percona-release setup ppg-18
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install the server, tde extension, and pgbackrest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install -y percona-postgresql-18 percona-pg-tde18 percona-pgbackrest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Verify Installation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-2-enable-pg_tde-and-create-keys">Step 2: Enable pg_tde and Create Keys&lt;/h2>
&lt;h3 id="21-configure-shared_preload_libraries">2.1 Configure shared_preload_libraries&lt;/h3>
&lt;p>Before we can use any of the encryption features, PostgreSQL needs to load the pg_tde library into memory at startup. You can do this by adding it to &lt;code>shared_preload_libraries&lt;/code>.&lt;/p>
&lt;p>You have two ways to handle this: the SQL way or the classic config file way.&lt;/p>
&lt;h4 id="option-a-sql-way-recommended">Option A: SQL way (recommended)&lt;/h4>
&lt;p>The fastest way is using &lt;code>ALTER SYSTEM&lt;/code>. This saves you from hunting through your file system for the right config file.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Set the library and restart to apply changes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql -c &lt;span class="s2">"ALTER SYSTEM SET shared_preload_libraries = 'pg_tde';"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl restart postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="option-b-manual-config-edit">Option B: Manual config edit&lt;/h4>
&lt;p>If you prefer managing your config files manually, find your &lt;code>postgresql.conf&lt;/code> and add the extension there.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Locate the config file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PG_CONF&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>psql -t -P &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>unaligned -c &lt;span class="s2">"show config_file;"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Open that file and find the 'shared_preload_libraries' line:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># shared_preload_libraries = 'pg_tde'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Restart PostgreSQL to load the library&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl restart postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: Regardless of which method you choose, a full restart of the PostgreSQL service is required. A simple reload won’t work for shared libraries.&lt;/p>
&lt;h3 id="22-create-the-key-provider">2.2 Create the Key Provider&lt;/h3>
&lt;p>Now that the extension is loaded, we need to tell pg_tde where to store its encryption keys.&lt;/p>
&lt;p>For this tutorial, we will use the File Provider (storing keys in a local file). In production environments, storing encryption keys locally on the PostgreSQL server can introduce security risks. To enhance security, pg_tde supports integration with external Key Management Systems (&lt;a href="https://docs.percona.com/pg-tde/global-key-provider-configuration/overview.html" target="_blank" rel="noopener noreferrer">KMS&lt;/a>) through a Global Key Provider interface.&lt;/p>
&lt;blockquote>
&lt;p>Note: While key files may be acceptable for local or testing environments, KMS integration is the recommended approach for production deployments.&lt;/p>&lt;/blockquote>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. Create a secure directory for the keys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /etc/postgresql/keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown postgres:postgres /etc/postgresql/keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">700&lt;/span> /etc/postgresql/keys&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- 2. Connect to PostgreSQL to configure TDE
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">EXTENSION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- 1. Define the global key provider
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_add_global_key_provider_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'global-file-provider'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'/etc/postgresql/keys/tde-global.per'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- 2. Create and Set the Principal Key
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_create_key_using_global_key_provider&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'global-master-key'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'global-file-provider'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_set_default_key_using_global_key_provider&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'global-master-key'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'global-file-provider'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- 3. Enable WAL encryption configuration
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SYSTEM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">wal_encrypt&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'on'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. Restart PostgreSQL to apply the encryption settings fully&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl restart postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-3-configure-pgbackrest">Step 3: Configure pgBackRest&lt;/h2>
&lt;p>Next, configure pgBackRest by defining the repository and stanza settings.
Before proceeding, it is important to understand how encryption is handled in this setup. pg_tde encrypts PostgreSQL data files on disk, protecting the live database. However, during WAL archiving, the pg_tde archive helper decrypts WAL records before passing them to pgBackRest. This means backups and archived WAL would be stored unencrypted unless repository encryption is enabled. To ensure backup data remains protected at rest, we enable pgBackRest repository encryption in this configuration. We configure the repository with a cipher type and key. Encryption is performed client side, ensuring data is secure before it is written to the repository.&lt;/p>
&lt;blockquote>
&lt;p>Note on Compression: In many pgBackRest deployments, compression is enabled to reduce backup size. However, when using pg_tde, database pages are already encrypted before pgBackRest processes them. Encryption randomizes the data blocks, making traditional compression algorithms such as gzip ineffective. For this reason, compression is disabled to avoid unnecessary CPU overhead.&lt;/p>&lt;/blockquote>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create the configuration file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/pgbackrest.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># In pg1-path use data directory path accordingly&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo bash -c &lt;span class="s2">"cat &lt;&lt;EOF > /etc/pgbackrest.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">[demo]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">pg1-path=/var/lib/postgresql/18/main
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">[global]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">repo1-path=/var/lib/pgbackrest
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">repo1-retention-full=2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">log-level-console=info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">start-fast=y
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"># PERFORMANCE: Encrypted data doesn't compress. We save CPU by disabling it.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">compress-type=none
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"># SECURITY: Since the helper sends decrypted data, we MUST encrypt the repo.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">repo1-cipher-type=aes-256-cbc
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">repo1-cipher-pass=Ifw7O0kTvdU5127L1gu8q3xVfWM61kl/NruTxQFWf9xP8A63Tg2IggRR9LUL9yJd
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"># TDE REQUIREMENT:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"># Asynchronous archiving is NOT supported with pg_tde.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">EOF"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="secure-the-configuration-file">Secure the Configuration File&lt;/h3>
&lt;p>Because this file contains your repository’s master passphrase, you must restrict access so only the postgres user can read it.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo chown postgres:postgres /etc/pgbackrest.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">600&lt;/span> /etc/pgbackrest.conf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create the repository directory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/pgbackrest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">750&lt;/span> /var/lib/pgbackrest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown postgres:postgres /var/lib/pgbackrest&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="understanding-the-settings">Understanding the Settings&lt;/h3>
&lt;ul>
&lt;li>&lt;code>pg1-path&lt;/code>: The data directory path to be backed up&lt;/li>
&lt;li>&lt;code>repo1-path&lt;/code>: The directory where backups will be stored&lt;/li>
&lt;li>&lt;code>repo1-retention-full&lt;/code>: Keep only two full backups&lt;/li>
&lt;li>&lt;code>start-fast=y&lt;/code>: Forces a checkpoint immediately when a backup starts. Without this, the backup would wait for the next scheduled checkpoint.&lt;/li>
&lt;li>&lt;code>compress-type=none&lt;/code>: By skipping compression, we eliminate unnecessary CPU overhead since compressing encrypted data blocks yields almost zero storage benefit.&lt;/li>
&lt;li>&lt;code>repo1-cipher-type&lt;/code> and &lt;code>repo1-cipher-pass&lt;/code>: Enable pgBackRest repository encryption. While pg_tde protects the live database files, these settings ensure that backup files and archived WAL stored in the repository are also encrypted at rest using AES-256&lt;/li>
&lt;/ul>
&lt;h2 id="step-4-wire-pgbackrest-into-postgresql-archiving">Step 4: Wire pgBackRest into PostgreSQL Archiving&lt;/h2>
&lt;p>pg_tde encrypts WAL files on disk. To allow pgBackRest to archive them correctly, we must decrypt them on the fly using the &lt;a href="https://docs.percona.com/pg-tde/command-line-tools/pg-tde-archive-decrypt.html" target="_blank" rel="noopener noreferrer">pg_tde_archive_decrypt&lt;/a> wrapper.&lt;/p>
&lt;p>Now, configure the &lt;code>archive_command&lt;/code>. This command tells PostgreSQL to pipe the WAL file through the decryption wrapper before handing it off to pgBackRest.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SYSTEM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wal_level&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'replica'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SYSTEM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">max_wal_senders&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SYSTEM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">archive_mode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'on'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SYSTEM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">archive_command&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'/usr/lib/postgresql/18/bin/pg_tde_archive_decrypt %f %p "pgbackrest --config=/etc/pgbackrest.conf --stanza=demo archive-push %%p"'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Restart PostgreSQL (adjust service name if needed, e.g., postgresql-18)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl restart postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-5-validate-encryption-on-disk">Step 5: Validate Encryption on Disk&lt;/h2>
&lt;p>Let’s verify that pg_tde is actually doing its job. We will create two tables, one standard and one encrypted and then inspect the raw files on disk to see the difference.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- 1. Create data: One clear text, one encrypted
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">EXISTS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clear_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">secret_info&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">TEXT&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">EXISTS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">crypt_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">secret_info&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">TEXT&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USING&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tde_heap&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Scenario A: The "Flushed" Data (Testing the Heap)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- We insert data and force a CHECKPOINT to push it to the .rel files.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clear_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">secret_info&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'FIND_ME_EASILY_123'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">crypt_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">secret_info&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'HIDDEN_FROM_DISK_456'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CHECKPOINT&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Scenario B: The "In-Flight" Data (Testing the WAL)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- We insert data but do NOT checkpoint. This data exists only in the WAL.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clear_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'VISIBLE_IN_WAL_789'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">crypt_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'HIDDEN_IN_WAL_000'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Force a WAL switch so the archive helper processes the segment immediately
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_switch_wal&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Verify TDE encryption status
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- For non-encrypted tables, this must return 'f' (false)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_is_encrypted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'clear_table'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- For encrypted table, this must return 't' (true)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_is_encrypted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'crypt_table'&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Locate the files on disk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>psql -t -P &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>unaligned -c &lt;span class="s2">"show data_directory;"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">CLEAR_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>psql -t -P &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>unaligned -c &lt;span class="s2">"SELECT pg_relation_filepath('clear_table');"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">CRYPT_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>psql -t -P &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>unaligned -c &lt;span class="s2">"SELECT pg_relation_filepath('crypt_table');"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LATEST_WAL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>ls -t &lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>/pg_wal &lt;span class="p">|&lt;/span> head -n 1&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Grep for the secret strings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">"--- CHECKING DATA FILES ---"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">"Checking Clear Table (Should Match):"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">grep -a &lt;span class="s2">"FIND_ME_EASILY_123"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">CLEAR_FILE&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">&amp;&amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> FOUND: Clear text is visible!"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">"Checking Encrypted Table (Should FAIL):"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">grep -a &lt;span class="s2">"HIDDEN_FROM_DISK_456"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">CRYPT_FILE&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> CLEAN: Encrypted text was NOT found."&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> -e &lt;span class="s2">"\n--- CHECKING THE WAL (In-Flight) ---"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">grep -a &lt;span class="s2">"VISIBLE_IN_WAL_789"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/pg_wal/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">LATEST_WAL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">&amp;&amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> FOUND: WAL contains clear text."&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">grep -a &lt;span class="s2">"HIDDEN_IN_WAL_000"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/pg_wal/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">LATEST_WAL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> CLEAN: WAL is successfully encrypted."&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># View the first few bytes of the WAL to see the "scrambled" nature&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">hexdump -C &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">DATA_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/pg_wal/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">LATEST_WAL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="p">|&lt;/span> head -n &lt;span class="m">20&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-6-initialize-the-stanza-and-run-a-full-backup">Step 6: Initialize the Stanza and Run a Full Backup&lt;/h2>
&lt;p>With the archive command configured, we can now initialize the stanza and run our first backup.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pgbackrest --stanza&lt;span class="o">=&lt;/span>demo stanza-create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pgbackrest --stanza&lt;span class="o">=&lt;/span>demo --type&lt;span class="o">=&lt;/span>full backup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pgbackrest --stanza&lt;span class="o">=&lt;/span>demo info&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-7-run-backup-integrity-tests">Step 7: Run Backup Integrity Tests&lt;/h2>
&lt;p>pgBackRest has a built-in verify command that checks the integrity of the files in the backup repo.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pgbackrest --stanza&lt;span class="o">=&lt;/span>demo verify&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="71-verify-repository-encryption">7.1 Verify Repository Encryption&lt;/h3>
&lt;p>We will now search the pgBackRest repository for the same “secret” strings we used earlier. Because we configured &lt;code>repo1-cipher-type=aes-256-cbc&lt;/code>, these should be completely invisible to &lt;code>grep&lt;/code>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define the repository path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">REPO_DIR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"/var/lib/pgbackrest/backup/demo"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">"--- SCANNING BACKUP REPOSITORY ---"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Search for the 'Flushed' secret&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo grep -r -a &lt;span class="s2">"HIDDEN_ON_DISK_456"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">REPO_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> CLEAN: Permanent data is encrypted in the repo."&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Search for the 'In-Flight' WAL secret&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># (This is the critical test for the archive helper/re-encryption flow)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo grep -r -a &lt;span class="s2">"HIDDEN_IN_WAL_000"&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">REPO_DIR&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">" -> CLEAN: WAL data is encrypted in the repo."&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># To be 100% sure we are not just failing to find the strings because of a typo, check the file type of a backup manifest or data block. Pick a random file from the repository&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">TARGET_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>find &lt;span class="si">${&lt;/span>&lt;span class="nv">REPO_DIR&lt;/span>&lt;span class="si">}&lt;/span> -type f -name &lt;span class="s2">"*.bundle"&lt;/span> -o -name &lt;span class="s2">"*.gz"&lt;/span> &lt;span class="p">|&lt;/span> head -n 1&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Run the 'file' command&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo file -s &lt;span class="s2">"&lt;/span>&lt;span class="nv">$TARGET_FILE&lt;/span>&lt;span class="s2">"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected result: It should return &lt;code>data&lt;/code>. If it returns PostgreSQL or ASCII text, encryption is not active.&lt;/p>
&lt;h2 id="step-8-restore-pg_tde-aware">Step 8: Restore (pg_tde-aware)&lt;/h2>
&lt;p>Restoring an encrypted cluster requires us to reverse the process. Since our backups are stored decrypted by pgBackRest, we use the &lt;a href="https://docs.percona.com/pg-tde/command-line-tools/pg-tde-restore-encrypt.html" target="_blank" rel="noopener noreferrer">pg_tde_restore_encrypt&lt;/a> wrapper to re-encrypt the WAL files as they are written back to disk.&lt;/p>
&lt;h3 id="81-stop-the-service">8.1 Stop the Service&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl stop postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="82-simulate-data-loss">8.2 Simulate Data Loss&lt;/h3>
&lt;p>The following command wipes the current data directory clean, ensuring we are restoring into a fresh environment.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">find /var/lib/postgresql/18/main -mindepth &lt;span class="m">1&lt;/span> -delete&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="83-restore-from-backup">8.3 Restore from Backup&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pgbackrest --stanza&lt;span class="o">=&lt;/span>demo restore --recovery-option&lt;span class="o">=&lt;/span>&lt;span class="nv">restore_command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'/usr/lib/postgresql/18/bin/pg_tde_restore_encrypt %f %p "pgbackrest --stanza=demo archive-get %%f %%p"'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="84-configure-the-restore-command">8.4 Configure the Restore Command&lt;/h3>
&lt;p>We used &lt;code>--recovery-option&lt;/code> in the restore command. This option writes the correct &lt;code>restore_command&lt;/code> for this recovery run and keeps the configuration in one place.&lt;/p>
&lt;p>&lt;code>pg_tde_restore_encrypt&lt;/code> is the required wrapper for pg_tde WAL restore: pgBackRest reads WALs from the repository in plain form, and this tool re-encrypts them as PostgreSQL writes them back to disk so the restored cluster remains encrypted.&lt;/p>
&lt;h3 id="85-start-postgresql">8.5 Start PostgreSQL&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl start postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-9-run-verification-tests">Step 9: Run Verification Tests&lt;/h2>
&lt;p>After restoring and starting the PostgreSQL server successfully, verify that the data was restored properly and also make sure that encrypted data can be retrieved.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- Verify data integrity (sample rows)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clear_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIMIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">crypt_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIMIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Verify TDE encryption status
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- For non-encrypted tables, this must return 'f' (false)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_is_encrypted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'clear_table'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- For encrypted table, this must return 't' (true)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_tde_is_encrypted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'crypt_table'&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="wrap-up">Wrap-up&lt;/h2>
&lt;p>Security often comes at the cost of operational complexity, but it doesn’t have to compromise recoverability. By pairing Percona’s solution for transparent data encryption (pg_tde) with pgBackRest, you can established a strategy that satisfies both security auditors and operations teams: your data is transparently encrypted on disk to meet strict compliance standards, while your backups remain consistent, verifiable, and easy to restore.&lt;/p>
&lt;p>While this walkthrough used a local file provider for simplicity it is highly discouraged to do so for any production or otherwise serious use cases. For this particular scenario, the focus was supposed to be on backup, please let us know if some similar articles about Key Management System (KMS) configuration is what you would be interested in.&lt;/p>
&lt;p>As you progress from this blog post to a production deployment, we recommend exploring a dedicated &lt;a href="https://docs.percona.com/pg-tde/global-key-provider-configuration/overview.html" target="_blank" rel="noopener noreferrer">KMS&lt;/a> solution to further harden your architecture against unauthorized access.&lt;/p>
&lt;p>Finally, be aware that to support archiving, the pg_tde wrapper decrypts WAL files before sending them to the repository. This means your backup repository currently holds unencrypted data. To close this security gap in production, you must ensure that encryption is enabled at the backup repository level so that your backups remain just as secure as your live database.&lt;/p>
&lt;p>Remember: While a backup is running, you should not change any WAL encryption settings, including:&lt;/p>
&lt;ul>
&lt;li>Global key provider operations (creating or changing)&lt;/li>
&lt;li>WAL encryption keys (creating or changing)&lt;/li>
&lt;li>The &lt;code>pg_tde.wal_encrypt&lt;/code> setting&lt;/li>
&lt;/ul>
&lt;p>The reason is that standbys or standalone clusters created from backups taken during these changes may fail to start during WAL replay and can also lead to corruption of encrypted data (tables, indexes, and other relations).&lt;/p></content:encoded><author>Shahid Ullah</author><category>PostgreSQL</category><category>PG_TDE</category><category>Security</category><category>Backups</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2026/03/pg_tde_pgbackrest_hu_382371ff2ceb1890.jpg"/><media:content url="https://percona.community/blog/2026/03/pg_tde_pgbackrest_hu_cdabd3203adf4359.jpg" medium="image"/></item><item><title>I Built an AI That Impersonates Me on Slack, and It Was Disturbingly Easy</title><link>https://percona.community/blog/2026/03/09/i-built-an-ai-that-impersonates-me-on-slack-and-it-was-disturbingly-easy/</link><guid>https://percona.community/blog/2026/03/09/i-built-an-ai-that-impersonates-me-on-slack-and-it-was-disturbingly-easy/</guid><pubDate>Mon, 09 Mar 2026 10:00:00 UTC</pubDate><description>I spend a lot of time in Slack. Most people in tech do. It’s where a lot of “work” happens such as quick questions, async decisions, the “hey can you look at this?” threads that never seem to end. It feels personal. You think you know who’s on the other end.</description><content:encoded>&lt;p>I spend a lot of time in Slack. Most people in tech do. It’s where a lot of “work” happens such as quick questions, async decisions, the “hey can you look at this?” threads that never seem to end. It feels personal. You think you know who’s on the other end.&lt;/p>
&lt;p>So, a few days back, I just asked myself, what would it actually take to have an AI respond to my DMs, pretending to be me?&lt;/p>
&lt;p>Turns out: a few hours, some TypeScript, and a token already sitting on my machine.&lt;/p>
&lt;h2 id="the-overall-design-idea">The overall design idea&lt;/h2>
&lt;p>The bot polls your Direct Messages (DMs) in Slack silently in the background using your real desktop token, no Slack admin approval, no OAuth app setup, no review process needed. Getting that token is straightforward: Slack’s desktop app stores your session in browser local storage, so one DevTools command gives you API access equivalent to the app itself.&lt;/p>
&lt;p>From there, each incoming DM is sent to the model with recent thread context plus a persona prompt built from your past conversations. The model returns structured output: reply text, emoji reaction, or silence. A lightweight rate limiter spaces requests to stay within free-tier constraints.&lt;/p>
&lt;p>In practice, a few conversation samples are enough for the model to mirror tone, vocabulary, and punctuation style. It also handles attachments and image messages sensibly, not just raw metadata.&lt;/p>
&lt;p>I wanted it to pretend it’s human, so I implemented constraints as to how it behaves. With these in place, if asked whether it’s an AI, it deflects with casual confusion.
To ensure that the bot does not get into sensitive topics like salary or politics these get redirected to “let’s talk in person.”. The bot is also explicitly limited to DMs. Group channels are hard-blocked in code.&lt;/p>
&lt;p>The first version worked in roughly two hours. The remaining time went into handling real-world rough edges such as rate limits, image handling, a 200-DM pagination ceiling, and Slack emoji-name validation.&lt;/p>
&lt;pre class="mermaid">
flowchart LR
slack(["Slack"])
bot["Bot running locally"]
ai(["Claude / Ollama"])
persona[/"Your writing samples"/]
slack -->|"incoming DMs"| bot
persona --> bot
bot &lt;-->|"generate reply in your voice"| ai
bot -->|"reply as you"| slack
&lt;/pre>
&lt;h2 id="the-uncomfortable-part">The Uncomfortable Part&lt;/h2>
&lt;p>Here’s what stuck with me after building this.&lt;/p>
&lt;p>Slack feels safe. It’s behind your company SSO. It’s where people share things they wouldn’t put in an email. With this bot excercise and realizing how easy this is, I felt I’ve broken something that felt secure. Was this even a morally correct thing to do overall? So far I felt safe, but now should I start to question messages I get on Slack the same as I do with some documents or links in emails? Overall I’m still undecided on how to think about the outcome of the experiment. While I’m excited, I’m also scared that I’ve broken something deeper.&lt;/p>
&lt;p>What I built here is, if you strip out the friendly framing: a system that reads every DM to a user, replies under their name in their tone, actively deflects if you try to verify whether it’s human, and does all of this indefinitely and silently from a laptop running in the background. If I fed this bot with enough background information and history, I’m almost certain, it could go unnotice for quite a long time. So the moral delimma between curiosity and ethical boundaries and the urge to inform people about it is real.&lt;/p>
&lt;p>I added ethical guardrails, but those are prompt instructions. They exist because I chose to write them. Someone building this without my good intent, simply wouldn’t have them. Yes, I hear you, this is getting a litte scary at times.&lt;/p>
&lt;h4 id="this-conversation-has-happened-without-me-ever-touching-the-keyboardyes-zsolt-was-aware">This conversation has happened, without me ever touching the keyboard….yes, Zsolt was aware!&lt;/h4>
&lt;p>
&lt;figure>&lt;img width="1167" height="1729" sizes="(max-width: 1167px) 100vw, 1167px" srcset="https://percona.community/blog/2026/03/impersonation-slack-conversation_hu_a4fa381239a3e154.webp 768w, https://percona.community/blog/2026/03/impersonation-slack-conversation_hu_714d6978257f70b3.webp 480w, https://percona.community/blog/2026/03/impersonation-slack-conversation_hu_2803eaf36655c082.webp 1167w" src="https://percona.community/blog/2026/03/impersonation-slack-conversation_hu_2803eaf36655c082.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="easy-is-relative-but-not-by-much">“Easy” Is Relative, But Not By Much&lt;/h2>
&lt;p>Core functionality (polling DMs, calling the API, posting replies) was working in under two hours, as stated above. Why do I repeat myself? Because it’s scary…&lt;/p>
&lt;p>The tooling: &lt;strong>&lt;a href="https://bun.sh" target="_blank" rel="noopener noreferrer">Bun&lt;/a>&lt;/strong>, a modern TypeScript runtime that made setup trivial. &lt;strong>&lt;a href="https://platform.claude.com/docs/en/api/client-sdks" target="_blank" rel="noopener noreferrer">Anthropic’s SDK&lt;/a>&lt;/strong>, clean API, takes a system prompt and a conversation and returns structured JSON. &lt;strong>&lt;a href="https://docs.slack.dev/apis/web-api/" target="_blank" rel="noopener noreferrer">Slack’s own API&lt;/a>&lt;/strong>, well-documented and permissive with desktop tokens.&lt;/p>
&lt;p>No specialised knowledge needed. Anyone motivated enough could reproduce this easily. Someone who does this professionally could build something considerably more capable, and that’s precisely where it gets more uncomfortable.&lt;/p>
&lt;h4 id="thats-how-the-cli-output-looks-like">That’s how the CLI output looks like&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">slack-bot$ bun run bot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ bun run src/index.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Running | mode: allowlist | backend: claude | review: off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] My user ID: U03A3PZHK5X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Mode: allowlist | Allowlist: U03QTQQHZFX, U83651WSX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Polling every 15s...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] D04BZ2BNABU: 1 new message(s) from [U83651WSX]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] New DM from U83651WSH — generating reply...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude call — est. ~933 input tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude tokens: 1049 in / 8 out
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Ignoring message from U83651WSX (AI chose no response)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Handled message from U83651WSX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] D04BZ2BNABU: 1 new message(s) from [U83651WSX]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] New DM from U83651WSX — generating reply...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude call — est. ~944 input tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude tokens: 1059 in / 23 out
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Handled message from U83651WSX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] D04BZ2BNABU: 1 new message(s) from [U83651WSX]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] New DM from U83651WSX — generating reply...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude call — est. ~976 input tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Claude tokens: 1085 in / 36 out
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] Handled message from U83651WSX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[slack-bot] D04BZ2BNABU: 1 new message(s) from [U83651WSX]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="thats-how-the-cli-helper-and-options-look-like">That’s how the CLI helper and options look like&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">slack-bot$ bun run bot --help
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ bun run src/index.ts --help
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Usage: slack-bot [options] [command]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Personal Slack bot that replies as you
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Options:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -V, --version output the version number
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --mode &lt;mode> Response mode: auto | away | allowlist | manual
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --review Enable review mode (approve before sending)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --no-review Disable review mode
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --allow &lt;user> Add user to allowlist (Slack user ID)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --interval &lt;secs> Poll interval in seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --backend &lt;name> AI backend: claude | ollama
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --config &lt;path> Path to config file (default: "config.json")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -h, --help display help for command
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Commands:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> context Manage active context
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> check-user [options] &lt;userId> Check whether a user's DM channel is found and reachable&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="what-it-looks-like-without-the-constraints">What It Looks Like Without the Constraints&lt;/h2>
&lt;p>What I built runs against Claude’s API with free-tier rate limits, small context window, a handful of persona examples, a throttle on message volume. Those constraints are real and also completely trivially removable.&lt;/p>
&lt;p>You can run the same thing with a local model, Llama 3, Mistral, take your pick from the open-weight models available on consumer hardware today, and it changes significantly.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>No rate limits.&lt;/strong> Every message gets answered immediately, without the 12-second pause between API calls. Response timing becomes indistinguishable from a fast typist.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>No token budget.&lt;/strong> Instead of a few hundred tokens of context, you can feed it your entire Slack history. Months, years of it. Every thread, every in-joke, every project reference. The model doesn’t just match your writing style, it knows what you’ve been working on, what you said about the Q3 roadmap in October, what you think about your manager.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>No API calls leaving your machine.&lt;/strong> Nothing logged externally. Invisible from a network perspective.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>With a large enough context window (Llama 3.1 supports 128k tokens, roughly 100,000 words), the last few &lt;em>months&lt;/em> fit. “Remember what we decided on Thursday?” doesn’t expose it anymore, because it actually has that conversation in its context.&lt;/p>
&lt;p>Seeing articles like &lt;a href="https://newsletter.pragmaticengineer.com/p/the-10x-overlemployed-engineer" target="_blank" rel="noopener noreferrer">that&lt;/a> make me wonder, how many people are out there already, doing exactly that as we speak…or do we?&lt;/p>
&lt;h2 id="a-few-things-worth-knowing">A Few Things Worth Knowing&lt;/h2>
&lt;p>This isn’t a call to panic. But it’s probably worth stopping for a second and questioning more what is happening around is.&lt;/p>
&lt;p>For anything that actually matters, financial, personal, strategic, verify out-of-band. A quick voice note or phone call costs almost nothing and resolves almost everything - at least until the video part also improves even further. I know people don’t like phone calls, especially in the developer ecosystem, but maybe we should reconsider this nowadays?&lt;/p>
&lt;p>Unusual patterns are worth noticing. Response timing that’s too consistent. Answers that are slightly generic when you’d expect specific. Deflection where you’d expect directness. None of these are proof of anything individually, but they’re worth filing away.&lt;/p>
&lt;p>The safe-space feeling Slack gives you is a product of habit, not architecture. Slack’s security model protects your data from outsiders. It doesn’t protect you from someone who has authenticated as themselves and is quietly running a process in the background. In the past this would be only a consideration for man-in-the middle attacks, nowadays it also may be a consideration for other cases as I have demonstrated.&lt;/p>
&lt;p>Specific questions still help, for now. “Remind me what we decided on Thursday?” trips up a system with limited context. But that window is closing as context windows grow.&lt;/p>
&lt;h2 id="why-did-i-do-it">Why did I do it?&lt;/h2>
&lt;p>I built this to see if it was possible. It was, faster than I expected, with tools that are widely available. The version I built in an evening is convincing enough for routine exchanges. A version with local inference and full conversation history would be convincing for most exchanges, including ones where you’re actively looking for tells.&lt;/p>
&lt;p>That gap between “afternoon project” and “genuinely hard to detect” is smaller than people assume and it’s shrinking. Better models, larger context windows, cheaper hardware, each of these individually makes impersonation easier; together they compound.&lt;/p>
&lt;p>The signals we relied up to now to establish trust in digital communication: name, avatar, writing style, shared history, plausible timing. These signals are all reproducible now, with effort that ranges from an afternoon to a weekend depending on how convincing you want to be.&lt;/p>
&lt;p>My grandma always used to say, that history repeats itself and you just have to wait long enough until “old” becomes “new and modern” again. Maybe simple things like &lt;a href="https://www.wsj.com/tech/personal-tech/why-every-family-needs-a-code-word-e077ab76" target="_blank" rel="noopener noreferrer">code words&lt;/a> is something to reconsider in this context, as a last chance to not get tricked.&lt;/p>
&lt;p>So please be a little curious about who you’re actually talking to and maybe agree on a code word in case you’re in doubts. And every now and then, just call them…which reminds me, that this might be worth another evening project research ;-).&lt;/p>
&lt;hr>
&lt;p>For the first time the source of a project of mine is not in my repository, as I still fight my inner fight with ethics.&lt;/p></content:encoded><author>Kai Wagner</author><category>Percona</category><category>Vibe Coding</category><category>Community</category><category>Security</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2026/03/vibe-coded-slack-impersonation_hu_d3d24ad7e0f85892.jpg"/><media:content url="https://percona.community/blog/2026/03/vibe-coded-slack-impersonation_hu_baef05eac1cdb322.jpg" medium="image"/></item><item><title>PostgreSQL 18 OIDC Authentication with Ping Identity using pg_oidc_validator</title><link>https://percona.community/blog/2026/03/04/postgresql-18-oidc-authentication-with-ping-identity-using-pg_oidc_validator/</link><guid>https://percona.community/blog/2026/03/04/postgresql-18-oidc-authentication-with-ping-identity-using-pg_oidc_validator/</guid><pubDate>Wed, 04 Mar 2026 00:00:00 UTC</pubDate><description>PostgreSQL 18 introduced native OAuth 2.0 authentication support, marking an important step towards modern, centralized identity-based access control. However, since every identity provider implements OpenID Connect (OIDC) slightly differently, PostgreSQL delegates token validation to external validator libraries. This is where Percona’s pg_oidc_validator extension comes in - it bridges PostgreSQL with any OIDC-compliant Identity Provider.</description><content:encoded>&lt;p>PostgreSQL 18 introduced native OAuth 2.0 authentication support, marking an important step towards modern, centralized identity-based access control. However, since every identity provider implements OpenID Connect (OIDC) slightly differently, PostgreSQL delegates token validation to external validator libraries. This is where Percona’s &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator" target="_blank" rel="noopener noreferrer">pg_oidc_validator&lt;/a> extension comes in - it bridges PostgreSQL with any OIDC-compliant Identity Provider.&lt;/p>
&lt;p>There are several identity and access management (IAM) solutions available today that enable Single Sign-On (SSO) using OAuth 2.0 and OpenID Connect. In an earlier blog by my colleague Zsolt, &lt;a href="https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/" target="_blank" rel="noopener noreferrer">OIDC in PostgreSQL: With Keycloak&lt;/a>, he demonstrated how PostgreSQL 18 can be integrated with Keycloak using pg_oidc_validator. In this post, we explore the same concept using &lt;a href="https://www.pingidentity.com/en/platform.html" target="_blank" rel="noopener noreferrer">Ping Identity&lt;/a> (PingOne).&lt;/p>
&lt;p>Ping Identity is widely used in enterprise environments for identity and access management. If you are in such an environment, integrating PostgreSQL directly with Ping Identity can provide access control.&lt;/p>
&lt;p>This blog is intended for PostgreSQL users, DBAs and customers who want to evaluate or deploy OIDC authentication using pg_oidc_validator. The goal is to provide a practical step-by-step walk-through to get a working setup.&lt;/p>
&lt;p>We will cover the following topics as part of this blog:&lt;/p>
&lt;ul>
&lt;li>Setting up PingOne environment&lt;/li>
&lt;li>Install PostgreSQL 18 from Packages&lt;/li>
&lt;li>Configure PostgreSQL for OAuth/OIDC authentication&lt;/li>
&lt;li>Test login using an OIDC flow&lt;/li>
&lt;/ul>
&lt;h1 id="setting-up-pingone-environment">Setting up PingOne environment&lt;/h1>
&lt;ol>
&lt;li>
&lt;p>Register a new &lt;a href="https://www.pingidentity.com/en/account/register.html" target="_blank" rel="noopener noreferrer">account&lt;/a>
&lt;figure>&lt;img width="2048" height="1222" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-account-register_hu_12be044e099b437f.webp 768w, https://percona.community/blog/2026/03/ping-account-register_hu_e8bbf5829b93ce4a.webp 480w, https://percona.community/blog/2026/03/ping-account-register_hu_9055dd798d72a910.webp 1400w" src="https://percona.community/blog/2026/03/ping-account-register_hu_9055dd798d72a910.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Fill in the required details to complete the profile
&lt;figure>&lt;img width="2048" height="1124" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-profile_hu_8001cf3b8230be9e.webp 768w, https://percona.community/blog/2026/03/ping-profile_hu_e4f3e833a9786365.webp 480w, https://percona.community/blog/2026/03/ping-profile_hu_c049b5662674813b.webp 1400w" src="https://percona.community/blog/2026/03/ping-profile_hu_c049b5662674813b.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The next step is to sign in to your Ping Identity &lt;a href="https://www.pingidentity.com/en/account/sign-on.html" target="_blank" rel="noopener noreferrer">account&lt;/a>
&lt;figure>&lt;img width="2048" height="1222" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-sign-on_hu_d6b1e032752b43a4.webp 768w, https://percona.community/blog/2026/03/ping-sign-on_hu_3d10c1608c016036.webp 480w, https://percona.community/blog/2026/03/ping-sign-on_hu_f28ddbf463ef421c.webp 1400w" src="https://percona.community/blog/2026/03/ping-sign-on_hu_f28ddbf463ef421c.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Upon successful Sign-in, we will see Ping Identity Administrator Console. In the left navigation panel, click on Environments -> &lt;strong>Environments +&lt;/strong> (marked in red).
&lt;figure>&lt;img width="2048" height="1006" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-admin-console_hu_bb2bc05711386872.webp 768w, https://percona.community/blog/2026/03/ping-admin-console_hu_ed88e9415810c091.webp 480w, https://percona.community/blog/2026/03/ping-admin-console_hu_e0c245faa5b60dcd.webp 1400w" src="https://percona.community/blog/2026/03/ping-admin-console_hu_e0c245faa5b60dcd.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Provide an environment name and click on Finish
&lt;figure>&lt;img width="1999" height="1101" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-environment_hu_e986e5bf05bd22ef.webp 768w, https://percona.community/blog/2026/03/ping-environment_hu_75234a977ab8e35f.webp 480w, https://percona.community/blog/2026/03/ping-environment_hu_1de0ea2de65f2cd9.webp 1400w" src="https://percona.community/blog/2026/03/ping-environment_hu_1de0ea2de65f2cd9.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Once the environment is created, click on Manage environment.
&lt;figure>&lt;img width="2048" height="1124" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-manage-environment_hu_ddbf301fe0bb34b1.webp 768w, https://percona.community/blog/2026/03/ping-manage-environment_hu_592a531bef34045.webp 480w, https://percona.community/blog/2026/03/ping-manage-environment_hu_3b5626b48f80dcd3.webp 1400w" src="https://percona.community/blog/2026/03/ping-manage-environment_hu_3b5626b48f80dcd3.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>In the left navigation panel, click on Applications -> Applications -> click on &lt;strong>Applications +&lt;/strong>. Fill the application name, select the application type as &lt;strong>Device Authorization&lt;/strong> and click on Save.
&lt;figure>&lt;img width="2048" height="1131" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-new-application_hu_3750e9ca63ae3c66.webp 768w, https://percona.community/blog/2026/03/ping-new-application_hu_a1f4eca8cc750a73.webp 480w, https://percona.community/blog/2026/03/ping-new-application_hu_aae3c2c62119b6f4.webp 1400w" src="https://percona.community/blog/2026/03/ping-new-application_hu_aae3c2c62119b6f4.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Upon successful creation, we will see generated &lt;strong>Client ID&lt;/strong> and &lt;strong>Issuer ID&lt;/strong>. The Issuer ID can be copied from under the &lt;em>Connection Details&lt;/em> section. Enable the toggle so that the application is Active.
&lt;figure>&lt;img width="2048" height="1127" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-created-application_hu_161d0a5fc9c9ba65.webp 768w, https://percona.community/blog/2026/03/ping-created-application_hu_a03b69a56ee7f9fe.webp 480w, https://percona.community/blog/2026/03/ping-created-application_hu_ef3be57d9c95f3d1.webp 1400w" src="https://percona.community/blog/2026/03/ping-created-application_hu_ef3be57d9c95f3d1.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Once the application is successfully created, we need to add a client scope. In the left navigation panel, click on Applications -> Resources -> OpenID Connect. You will see a section called &lt;em>Scopes&lt;/em> under which there is a &lt;strong>+ Add Scope&lt;/strong> button.
&lt;figure>&lt;img width="3018" height="1544" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-click-add-scope_hu_4fc81cbce323a527.webp 768w, https://percona.community/blog/2026/03/ping-click-add-scope_hu_bab8af4beca60186.webp 480w, https://percona.community/blog/2026/03/ping-click-add-scope_hu_a9b2f79e0db03afb.webp 1400w" src="https://percona.community/blog/2026/03/ping-click-add-scope_hu_a9b2f79e0db03afb.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Upon clicking the Add scope button, we need to fill the &lt;em>Scope name&lt;/em> and click on Save. In our example, we are creating a scope called &lt;em>pgscope&lt;/em>
&lt;figure>&lt;img width="3018" height="1644" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-add-scope-name_hu_93a1219869dd9809.webp 768w, https://percona.community/blog/2026/03/ping-add-scope-name_hu_848a29f3da8a252.webp 480w, https://percona.community/blog/2026/03/ping-add-scope-name_hu_958186a62af4db1b.webp 1400w" src="https://percona.community/blog/2026/03/ping-add-scope-name_hu_958186a62af4db1b.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Now, let’s assign the custom scope we created to our client application. In the left navigation panel, click on Applications -> Applications. Select the application &lt;em>postgres&lt;/em> which we created previously and click on &lt;em>Resource Access&lt;/em>
&lt;figure>&lt;img width="3018" height="1644" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-application-config_hu_eac7763fd705463c.webp 768w, https://percona.community/blog/2026/03/ping-application-config_hu_1ab1f404e0e3bab4.webp 480w, https://percona.community/blog/2026/03/ping-application-config_hu_e869ced0fa01a34f.webp 1400w" src="https://percona.community/blog/2026/03/ping-application-config_hu_e869ced0fa01a34f.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>From the list of available scopes, select the custom scope we created and click on Save. This will assign the scope to our application.
&lt;figure>&lt;img width="3018" height="1644" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-application-add-scope_hu_5e882a007774324.webp 768w, https://percona.community/blog/2026/03/ping-application-add-scope_hu_be42af2bf3a801b4.webp 480w, https://percona.community/blog/2026/03/ping-application-add-scope_hu_abe259fdf76f4369.webp 1400w" src="https://percona.community/blog/2026/03/ping-application-add-scope_hu_abe259fdf76f4369.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The next step is to add a new user. In the left navigation panel, click on Directory -> Users and click on &lt;strong>Users +&lt;/strong> sign.&lt;/p>
&lt;p>Fill the username field. For our exercise, we are creating a user called &lt;strong>employees.&lt;/strong> In some identity providers (IdPs), it is possible to customize tokens and control how certain claims (including the &lt;strong>sub&lt;/strong> - subject claim) are generated or mapped. However, it is important to note that while Ping Identity allows customization of the ID token, the access token claims (including sub) for the default OpenID Connect resource cannot be customized. The value of sub in the access token is generated and managed internally by PingOne and cannot be altered, mapped, or derived from another attribute (such as email or username). As a result, PostgreSQL must be configured to work with the sub value exactly as issued in the access token by PingOne.
&lt;figure>&lt;img width="2048" height="1127" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-create-user_hu_b9ed6594bddf3fc7.webp 768w, https://percona.community/blog/2026/03/ping-create-user_hu_53e3468473529fb4.webp 480w, https://percona.community/blog/2026/03/ping-create-user_hu_63ab10a5807db6e1.webp 1400w" src="https://percona.community/blog/2026/03/ping-create-user_hu_63ab10a5807db6e1.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Enable the user by turning the toggle “ON” and set a password.
&lt;figure>&lt;img width="2048" height="1124" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-enable-user_hu_ee6792c220f50b1a.webp 768w, https://percona.community/blog/2026/03/ping-enable-user_hu_44f601f57155f806.webp 480w, https://percona.community/blog/2026/03/ping-enable-user_hu_fef5b33888a4960.webp 1400w" src="https://percona.community/blog/2026/03/ping-enable-user_hu_fef5b33888a4960.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h1 id="install-postgresql-18-from-packages">Install PostgreSQL 18 from Packages&lt;/h1>
&lt;p>Since OAuth support is only available starting with PostgreSQL 18, we need a PostgreSQL server of at least this version.In the guide, we will install PostgreSQL 18 using Percona’s official packages.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Ensure that the &lt;a href="https://docs.percona.com/percona-software-repositories/installing.html" target="_blank" rel="noopener noreferrer">percona-release&lt;/a> is already installed and configured on your system. You can refer to the official Percona documentation for setup instructions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>For this exercise, the steps are demonstrated on Ubuntu 24.04&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="enable-the-postgresql-18-repository">Enable the PostgreSQL 18 repository&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo percona-release enable-only ppg-18.2 release
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt update&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="install-postgresql-18">Install PostgreSQL 18&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt install -y percona-postgresql-18&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="install-oauth-support-for-libpq">Install OAuth Support for libpq&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt install libpq-oauth&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h1 id="configure-postgresql-for-oauthoidc-authentication">Configure PostgreSQL for OAuth/OIDC authentication&lt;/h1>
&lt;h2 id="install-pg_oidc_validator">Install pg_oidc_validator&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt install percona-pg-oidc-validator18&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="setting-up-a-sample-use-case-for-oidc-based-access">Setting Up a Sample Use Case for OIDC-Based Access&lt;/h2>
&lt;p>Imagine the following use case:&lt;/p>
&lt;ul>
&lt;li>A company regularly generates promotional discount codes and stores them in a table called dcode inside a database named promo&lt;/li>
&lt;li>A new discount code is generated and added to this table every day.&lt;/li>
&lt;li>The company wants all employees to be able to access the latest code whenever needed.&lt;/li>
&lt;li>For simplicity in this demonstration, employees retrieve the code by connecting to the database and querying the table directly.&lt;/li>
&lt;li>To avoid managing individual database accounts for every employee, access is not tied to separate user credentials.&lt;/li>
&lt;li>Instead, authentication to the database is handled through the company’s SSO system, allowing employees to connect using their existing corporate identity.&lt;/li>
&lt;/ul>
&lt;pre class="mermaid">
---
config:
theme: neutral
---
architecture-beta
group company_network(cloud)[Company Network]
service employee(user)[Employee] in company_network
service sso(server)[Company SSO PingIdentity] in company_network
service promo_db(database)[Promo Database] in company_network
service code_gen(server)[Daily Code Generator] in company_network
code_gen:B --> T:promo_db
employee:R --> L:sso
sso:R --> L:promo_db
&lt;/pre>
&lt;p>&lt;strong>Let’s connect to PostgreSQL:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo -u postgres psql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Create a database:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DATABASE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">promo&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">promo&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Add a table to store discount codes:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dcode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">GENERATED_AT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TIMESTAMP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">default&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dcode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'SAVENOW'&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Create the access user:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ROLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">employees&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LOGIN&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dcode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">employees&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="configure-oauth-access-in-postgresql">Configure OAuth access in PostgreSQL:&lt;/h2>
&lt;p>In order for users to connect using OAuth and authenticate through the Ping Identity server, we need to create an identity map and add an entry for such access on PostgreSQL’s authentication configuration file.&lt;/p>
&lt;p>Edit the identity mapping configuration file and add an entry, which we will call &lt;em>oidc&lt;/em>, mapping connections originated by a system user identified with a sub ID. For this exercise, we allow any string to match&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo vim /etc/postgresql/18/main/pg_ident.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># MAPNAME SYSTEM-USERNAME DATABASE-USERNAME&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oidc /^&lt;span class="o">(&lt;/span>.*&lt;span class="o">)&lt;/span>$ employees&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Next, edit the authentication configuration file. The configuration file &lt;em>pg_hba.conf&lt;/em> acts as a sort of firewall for connections. With the below line, we are instructing PostgreSQL to allow all connections coming from any network that attempt to access the database promo as user employees using the new authentication method &lt;em>oauth&lt;/em>. We are also indicating that the authentication provider is Ping Identity and the scope is &lt;em>openid&lt;/em>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo vim /etc/postgresql/18/main/pg_hba.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE DATABASE USER ADDRESS METHOD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">host promo employees 0.0.0.0/0 oauth &lt;span class="nv">issuer&lt;/span>&lt;span class="o">=&lt;/span>https://auth.pingone.com.au/64935f69-5a0a-4b69-a8bd-46967d218303/as &lt;span class="nv">scope&lt;/span>&lt;span class="o">=&lt;/span>pgscope &lt;span class="nv">map&lt;/span>&lt;span class="o">=&lt;/span>oidc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Note:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Place this entry after the existing local rules and just before the replication rules in pg_hba.conf. PostgreSQL evaluates pg_hba.conf from top to bottom, and the first matching rule is applied. Putting the OAuth rule earlier ensures that connections to database promo as user employees use OAuth instead of falling back to password authentication.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>In production environments, restrict the IP range instead of using &lt;em>0.0.0.0/0&lt;/em>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The &lt;code>oauth_issuer&lt;/code> must exactly match the &lt;strong>Issuer ID&lt;/strong> from your PingOne environment. The Issuer URL is unique to each PingOne environment and contains your environment UUID. It typically follows this format: &lt;code>https://auth.pingone.com.au/&lt;ENVIRONMENT_UUID>/as&lt;/code>. Replace &lt;code>&lt;ENVIRONMENT_UUID>&lt;/code> with the actual value from your PingOne environment. Do not copy the placeholder value directly.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="enabling-the-pg_oidc_validation-extension">Enabling the pg_oidc_validation extension&lt;/h2>
&lt;p>Edit the postgresql.conf and add below lines&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo vim /etc/postgresql/18/main/postgresql.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">oauth_validator_libraries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'pg_oidc_validator'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The validator uses the &lt;code>sub&lt;/code> claim from the access token by default. Hence, we need not explicitly set &lt;code>pg_oidc_validator.authn_field=sub&lt;/code>. The sub claim is defined by the OpenID Connect specification as a stable and unique identifier for a user within an identity provider (IdP). It is intended to uniquely represent a user and remain consistent across authentication sessions.&lt;/p>
&lt;p>PostgreSQL does not interpret or transform this value. The validator extracts the configured claim and PostgreSQL compares it against a database role or an entry in pg_ident.conf. If the value does not match the expected role or mapping, authentication will fail, even if the token itself is valid.&lt;/p>
&lt;p>In this setup with Ping Identity, only the sub claim can be used for authentication with the default OpenID Connect resource.&lt;/p>
&lt;h2 id="reload-the-configuration">Reload the configuration&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo -u postgres psql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT pg_reload_conf&lt;span class="o">()&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="monitor-the-server-logs">Monitor the server logs&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo tail -f /var/log/postgresql/postgresql-18-main.log&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h1 id="test-login-using-an-oidc-flow">Test login using an OIDC flow&lt;/h1>
&lt;p>&lt;strong>Connecting to the database:&lt;/strong>&lt;/p>
&lt;p>For this quick connection test, we use psql to connect to the promo database as the employees user, explicitly specifying the host IP along with the &lt;strong>oauth_issuer&lt;/strong> and &lt;strong>client_id&lt;/strong>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">psql &lt;span class="s1">'host=127.0.0.1 user=employees dbname=promo oauth_issuer=https://auth.pingone.com.au/64935f69-5a0a-4b69-a8bd-46967d218303/as oauth_client_id=1e892f71-09d7-4ed6-a534-0dc888d39c7c'&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>By connecting via the host IP address rather than the local socket, PostgreSQL treats this as a host-based connection, ensuring that the OAuth configuration in pg_hba.conf is applied. The authentication is handled by PostgreSQL’s authentication framework, which uses OIDC with Ping Identity as the identity provider to validate the token.&lt;/p>
&lt;p>We will see a prompt on the console with the URL and activation code.You will notice an activation code &lt;strong>XXXX-XXX.&lt;/strong> Example shown below:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Visit https://auth.pingone.com.au/64935f69-5a0a-4b69-a8bd-46967d218303/device and enter the code: 7KK7-88DK&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Upon clicking the URL, it will prompt you to log in with the &lt;strong>employee's&lt;/strong> user, which we created during the PingOne environment setup.
&lt;figure>&lt;img width="2048" height="1126" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-user-login_hu_d40687e9a35dfaf7.webp 768w, https://percona.community/blog/2026/03/ping-user-login_hu_ae7bb3a85b5eccf2.webp 480w, https://percona.community/blog/2026/03/ping-user-login_hu_fb39239858342406.webp 1400w" src="https://percona.community/blog/2026/03/ping-user-login_hu_fb39239858342406.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Next, it will prompt you to enter the activation code.
&lt;figure>&lt;img width="2048" height="1126" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-activation-code_hu_bf3eb7e5a3de10de.webp 768w, https://percona.community/blog/2026/03/ping-activation-code_hu_5b2bacc8ba001591.webp 480w, https://percona.community/blog/2026/03/ping-activation-code_hu_8648a4b24a2bfa39.webp 1400w" src="https://percona.community/blog/2026/03/ping-activation-code_hu_8648a4b24a2bfa39.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Approve access for the application, and that’s it! The user has now been successfully authenticated via OIDC.
&lt;figure>&lt;img width="2048" height="1126" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/03/ping-approve-user_hu_7ad1cf133e0c3adb.webp 768w, https://percona.community/blog/2026/03/ping-approve-user_hu_3c2a1e99169c9e7d.webp 480w, https://percona.community/blog/2026/03/ping-approve-user_hu_29ace334305789da.webp 1400w" src="https://percona.community/blog/2026/03/ping-approve-user_hu_29ace334305789da.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Return to the PostgreSQL prompt and you should see that the login to the promo database is successful. You can now query the &lt;em>dcode&lt;/em> table to fetch the discount code.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">psql &lt;span class="s1">'host=127.0.0.1 user=employees dbname=promo oauth_issuer=https://auth.pingone.com.au/64935f69-5a0a-4b69-a8bd-46967d218303/as oauth_client_id=1e892f71-09d7-4ed6-a534-0dc888d39c7c'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Visit https://auth.pingone.com.au/64935f69-5a0a-4b69-a8bd-46967d218303/device and enter the code: 7KK7-88DK
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql &lt;span class="o">(&lt;/span>18.2 - Percona Server &lt;span class="k">for&lt;/span> PostgreSQL 18.2.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SSL connection &lt;span class="o">(&lt;/span>protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: postgresql&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Type &lt;span class="s2">"help"&lt;/span> &lt;span class="k">for&lt;/span> help.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">promo&lt;/span>&lt;span class="o">=&lt;/span>> &lt;span class="k">select&lt;/span> * from dcode&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> code &lt;span class="p">|&lt;/span> generated_at
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---------+----------------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SAVENOW &lt;span class="p">|&lt;/span> 2026-02-13 09:40:13.109801
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>&lt;span class="m">1&lt;/span> row&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With the steps shown in this guide, we now have a working end-to-end setup using OIDC authentication and device flow login. From here, the same model can be extended to real-world enterprise environments with tighter network restrictions and role mapping.&lt;/p>
&lt;p>If you run into issues while setting up pg_oidc_validator or integrating PostgreSQL with Ping Identity, check the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">community forums&lt;/a> first, chances are someone in the community may already have encountered a similar issue. If not, feel free to open a &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/issues" target="_blank" rel="noopener noreferrer">discussion&lt;/a> or raise a request for help.&lt;/p></content:encoded><author>Mohit Joshi</author><category>PostgreSQL</category><category>OIDC</category><category>Security</category><category>PingIdentity</category><category>pg_oidc_validator</category><media:thumbnail url="https://percona.community/blog/2026/03/ping-oidc-banner_hu_564b4dbec09e6232.jpg"/><media:content url="https://percona.community/blog/2026/03/ping-oidc-banner_hu_a5d1fbb43e7fe2e7.jpg" medium="image"/></item><item><title>Hardening MySQL: Practical Security Strategies for DBAs</title><link>https://percona.community/blog/2026/03/02/hardening-mysql-practical-security-strategies-for-dbas/</link><guid>https://percona.community/blog/2026/03/02/hardening-mysql-practical-security-strategies-for-dbas/</guid><pubDate>Mon, 02 Mar 2026 00:00:00 UTC</pubDate><description>MySQL Security Best Practices: A Practical Guide for Locking Down Your Database Introduction MySQL runs just about everywhere. I’ve seen it behind small personal projects, internal tools, SaaS platforms, and large enterprise systems handling serious transaction volume. When your database sits at the center of everything, it becomes part of your security perimeter whether you planned it that way or not. And that makes it a target.</description><content:encoded>&lt;h1 id="mysql-security-best-practices-a-practical-guide-for-locking-down-your-database">MySQL Security Best Practices: A Practical Guide for Locking Down Your Database&lt;/h1>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>MySQL runs just about everywhere. I’ve seen it behind small personal projects, internal tools, SaaS platforms, and large enterprise systems handling serious transaction volume. When your database sits at the center of everything, it becomes part of your security perimeter whether you planned it that way or not. And that makes it a target.&lt;/p>
&lt;p>Securing MySQL isn’t about flipping one magical setting and calling it done. It’s about layers. Tight access control. Encrypted connections. Clear visibility into what’s happening on the server. And operational discipline that doesn’t drift over time.&lt;/p>
&lt;p>In this guide, I’m going to walk through practical MySQL security best practices that you can apply right away. These are the kinds of checks and hardening steps that reduce real risk in real environments, and help build a database platform that stays resilient under pressure.&lt;/p>
&lt;hr>
&lt;h2 id="1-principle-of-least-privilege">1. Principle of Least Privilege&lt;/h2>
&lt;p>One of the most common security mistakes is over-granting privileges.
Applications and users should have only the permissions they absolutely
need.&lt;/p>
&lt;h3 id="bad-practice">Bad Practice&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ALL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">PRIVILEGES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'appuser'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'10.%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="better-approach">Better Approach&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">appdb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'appuser'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'10.%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="recommendations">Recommendations&lt;/h3>
&lt;ul>
&lt;li>Avoid global privileges unless absolutely required&lt;/li>
&lt;li>Restrict users by host whenever possible&lt;/li>
&lt;li>Separate admin accounts from application accounts&lt;/li>
&lt;li>Use different credentials for read-only vs write operations&lt;/li>
&lt;/ul>
&lt;h3 id="audit-existing-privileges">Audit Existing Privileges&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">host&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Select_priv&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Insert_priv&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Update_priv&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Delete_priv&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h2 id="2-strong-authentication--password-policies">2. Strong Authentication &amp; Password Policies&lt;/h2>
&lt;p>Weak credentials remain one of the easiest attack vectors.&lt;/p>
&lt;h3 id="enable-password-validation">Enable Password Validation&lt;/h3>
&lt;p>component_validate_password is MySQL’s modern password policy engine. Think of it as a gatekeeper for credential quality. Every time someone tries to set or change a password, it checks whether that password meets your defined security standards before letting it in.&lt;/p>
&lt;p>It replaces the older validate_password plugin with a component-based architecture that is more flexible and better aligned with MySQL 8.x design.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">INSTALL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">COMPONENT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'file://component_validate_password'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="what-it-does">What It Does&lt;/h3>
&lt;p>When enabled, it enforces rules such as:&lt;/p>
&lt;ul>
&lt;li>Minimum password length&lt;/li>
&lt;li>Required mix of character types&lt;/li>
&lt;li>Dictionary file checks&lt;/li>
&lt;li>Strength scoring&lt;/li>
&lt;/ul>
&lt;p>If a password fails policy, the statement is rejected before the credential is stored.&lt;/p>
&lt;h3 id="why-it-matters">Why It Matters&lt;/h3>
&lt;p>Weak passwords remain one of the most common entry points in database breaches. This component reduces risk by enforcing baseline credential hygiene automatically, instead of relying on developer discipline.&lt;/p>
&lt;h3 id="recommended-policies">Recommended Policies&lt;/h3>
&lt;ul>
&lt;li>Minimum length: 14+ characters&lt;/li>
&lt;li>Require mixed case, numbers, and symbols&lt;/li>
&lt;li>Enable dictionary checks&lt;/li>
&lt;li>Enable username checks&lt;/li>
&lt;/ul>
&lt;h3 id="remove-anonymous-accounts">Remove Anonymous Accounts&lt;/h3>
&lt;h4 id="find-anonymous-users">Find Anonymous Users&lt;/h4>
&lt;p>Anonymous users have an empty User field.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">host&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">''&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you see rows returned, those are anonymous accounts.&lt;/p>
&lt;h3 id="drop-anonymous-users">Drop Anonymous Users&lt;/h3>
&lt;p>In modern MySQL versions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">DROP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">''&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'localhost'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">DROP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">''&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Adjust the Host value based on what your query returned.&lt;/p>
&lt;h3 id="why-this-matters">Why This Matters&lt;/h3>
&lt;p>Anonymous users:&lt;/p>
&lt;ul>
&lt;li>Allow login without credentials&lt;/li>
&lt;li>May have default privileges in some distributions&lt;/li>
&lt;li>Increase the attack surface unnecessarily&lt;/li>
&lt;/ul>
&lt;p>In hardened environments, there should be zero accounts with an empty username. Every identity should be explicit, accountable, and least-privileged.&lt;/p>
&lt;h2 id="3-encryption-everywhere">3. Encryption Everywhere&lt;/h2>
&lt;p>Encryption protects data both in transit and at rest.&lt;/p>
&lt;h3 id="enable-transparent-data-encryption-tde">Enable Transparent Data Encryption (TDE)&lt;/h3>
&lt;p>See my January 13 post for a deep dive into Transparent Data Encryption:
&lt;a href="https://percona.community/blog/2026/01/13/configuring-the-component-keyring-in-percona-server-and-pxc-8.4/" target="_blank" rel="noopener noreferrer">Configuring the Component Keyring in Percona Server and PXC 8.4&lt;/a>&lt;/p>
&lt;h3 id="enable-tls-for-connections">Enable TLS for Connections&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">require_secure_transport&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">ON&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="verify-ssl-usage">Verify SSL Usage&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Ssl_cipher'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="encryption-areas-to-consider">Encryption Areas to Consider&lt;/h3>
&lt;ul>
&lt;li>Client-server connections&lt;/li>
&lt;li>Replication channels&lt;/li>
&lt;li>Backups and snapshot storage&lt;/li>
&lt;li>Disk-level encryption&lt;/li>
&lt;/ul>
&lt;h2 id="4-patch-management--version-hygiene">4. Patch Management &amp; Version Hygiene&lt;/h2>
&lt;p>Running outdated MySQL versions is equivalent to leaving known
vulnerabilities exposed.&lt;/p>
&lt;h3 id="maintenance-strategy">Maintenance Strategy&lt;/h3>
&lt;ul>
&lt;li>Track vendor security advisories&lt;/li>
&lt;li>Apply minor updates regularly&lt;/li>
&lt;li>Test patches in staging before production rollout&lt;/li>
&lt;li>Avoid unsupported MySQL versions&lt;/li>
&lt;/ul>
&lt;h3 id="check-version">Check Version&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VERSION&lt;/span>&lt;span class="p">();&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="5-logging-auditing-and-monitoring">5. Logging, Auditing, and Monitoring&lt;/h2>
&lt;p>Security without visibility is blind defense, enable Audit Logging.&lt;/p>
&lt;h3 id="1-audit_log-plugin-legacy-model">1. audit_log Plugin (Legacy Model)&lt;/h3>
&lt;h4 id="installation">Installation&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">INSTALL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PLUGIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">audit_log&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SONAME&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'audit_log.so'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="verify">Verify&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PLUGINS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'audit%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="2-audit_log_filter-component-modern-model">2. audit_log_filter Component (Modern Model)&lt;/h3>
&lt;p>Introduced in MySQL 8 to provide a more flexible and granular alternative to the older plugin model.&lt;/p>
&lt;h4 id="installation-1">Installation&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">INSTALL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">COMPONENT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'file://component_audit_log_filter'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="verify-1">Verify&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">component&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="architecture-difference">Architecture Difference&lt;/h4>
&lt;p>Instead of a single global policy, you create:&lt;/p>
&lt;ul>
&lt;li>Filters (define what to log)&lt;/li>
&lt;li>Users assigned to filters&lt;/li>
&lt;/ul>
&lt;p>It’s granular and rule-driven.&lt;/p>
&lt;h3 id="auditing-key-events">Auditing Key Events&lt;/h3>
&lt;ul>
&lt;li>Failed logins&lt;/li>
&lt;li>Privilege changes&lt;/li>
&lt;li>Schema modifications&lt;/li>
&lt;li>Unusual query activity&lt;/li>
&lt;/ul>
&lt;h3 id="references">References:&lt;/h3>
&lt;ol>
&lt;li>&lt;a href="https://percona.community/blog/2025/09/18/audit-log-filter-component/" target="_blank" rel="noopener noreferrer">Audit Log Filter Component
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2025/10/08/audit-log-filters-part-ii/" target="_blank" rel="noopener noreferrer">Audit Log Filters Part II
&lt;/a>&lt;/li>
&lt;/ol>
&lt;h3 id="useful-metrics">Useful Metrics&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Aborted_connects'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Connections'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="6-secure-configuration-hardening">6. Secure Configuration Hardening&lt;/h2>
&lt;p>A secure baseline configuration reduces risk from common attack
patterns.&lt;/p>
&lt;h3 id="recommended-settings">Recommended Settings&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">ini&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="na">local_infile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">OFF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">secure_file_priv&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">/var/lib/mysql-files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">sql_mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">"STRICT_ALL_TABLES"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">secure-log-path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">/var/log/mysql&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="why-these-matter">Why These Matter&lt;/h3>
&lt;ul>
&lt;li>Prevent arbitrary file imports&lt;/li>
&lt;li>Reduce filesystem abuse&lt;/li>
&lt;li>Restrict data export/import locations&lt;/li>
&lt;/ul>
&lt;h2 id="7-backup-security">7. Backup Security&lt;/h2>
&lt;p>Backups often contain everything an attacker wants.&lt;/p>
&lt;h3 id="backup-best-practices">Backup Best Practices&lt;/h3>
&lt;ul>
&lt;li>Encrypt backups&lt;/li>
&lt;li>Restrict filesystem permissions&lt;/li>
&lt;li>Store offsite copies securely&lt;/li>
&lt;li>Rotate backup credentials&lt;/li>
&lt;li>Verify restore procedures regularly&lt;/li>
&lt;/ul>
&lt;h3 id="example-permission-check">Example Permission Check&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ls -l /backup/mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="8-replication--cluster-security">8. Replication &amp; Cluster Security&lt;/h2>
&lt;p>Replication is not just a data distribution feature. It is a persistent, privileged communication channel between servers. If misconfigured, it can become a lateral movement pathway inside your infrastructure. Treat every replication link as a trusted but tightly controlled corridor.&lt;/p>
&lt;p>Principle: Replication Is a Privileged Service Account&lt;/p>
&lt;p>Replication users require elevated capabilities. They must be isolated, tightly scoped, and monitored like any other service identity.&lt;/p>
&lt;h3 id="secure-replication-users">Secure Replication Users&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'repl'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'10.%'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">IDENTIFIED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'strongpassword'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">REQUIRE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SSL&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REPLICATION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REPLICA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'repl'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'10.%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Hardening considerations:&lt;/p>
&lt;ul>
&lt;li>Restrict host patterns as narrowly as possible. Avoid % whenever feasible.&lt;/li>
&lt;li>Require SSL or X.509 certificate authentication.&lt;/li>
&lt;li>Enforce strong password policies or use a secrets manager.&lt;/li>
&lt;li>Disable interactive login capability if applicable.&lt;/li>
&lt;/ul>
&lt;h3 id="encrypt-replication-traffic">Encrypt Replication Traffic&lt;/h3>
&lt;p>Replication traffic may include sensitive row data, DDL statements, and metadata. Always encrypt it.&lt;/p>
&lt;p>At minimum:&lt;/p>
&lt;ul>
&lt;li>Enable require_secure_transport=ON&lt;/li>
&lt;li>Configure TLS certificates on source and replica&lt;/li>
&lt;li>Set replication channel to use SSL:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">CHANGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REPLICATION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SOURCE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SOURCE_SSL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SOURCE_SSL_CA&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'/path/ca.pem'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SOURCE_SSL_CERT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'/path/client-cert.pem'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SOURCE_SSL_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'/path/client-key.pem'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For MySQL Group Replication or InnoDB Cluster:&lt;/p>
&lt;ul>
&lt;li>Enable group communication SSL&lt;/li>
&lt;li>Validate certificate identity&lt;/li>
&lt;li>Use dedicated replication networks&lt;/li>
&lt;/ul>
&lt;h3 id="binary-log-and-relay-log-protection">Binary Log and Relay Log Protection&lt;/h3>
&lt;p>Replication relies on binary logs. Protect them.&lt;/p>
&lt;ul>
&lt;li>Set binlog_encryption=ON&lt;/li>
&lt;li>Set relay_log_info_repository=TABLE&lt;/li>
&lt;li>Restrict filesystem access to log directories&lt;/li>
&lt;li>Monitor log retention policies&lt;/li>
&lt;/ul>
&lt;p>Compromised binary logs can reveal historical data changes.&lt;/p>
&lt;h2 id="9-continuous-security-reviews">9. Continuous Security Reviews&lt;/h2>
&lt;p>Security is not a one-time checklist. Regular audits help catch
configuration drift and evolving threats.&lt;/p>
&lt;h3 id="suggested-review-cadence">Suggested Review Cadence&lt;/h3>
&lt;ul>
&lt;li>Weekly: failed login review&lt;/li>
&lt;li>Monthly: privilege audits&lt;/li>
&lt;li>Quarterly: configuration review&lt;/li>
&lt;li>Semiannually: full security assessment&lt;/li>
&lt;/ul>
&lt;h2 id="security-checklist-summary">Security Checklist Summary&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Area&lt;/th>
&lt;th>Key Action&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Access Control&lt;/td>
&lt;td>Least privilege grants&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Authentication&lt;/td>
&lt;td>Strong password policies&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encryption&lt;/td>
&lt;td>TLS + encrypted storage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Updates&lt;/td>
&lt;td>Regular patching&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Monitoring&lt;/td>
&lt;td>Audit logging enabled&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Configuration&lt;/td>
&lt;td>Harden defaults&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Backups&lt;/td>
&lt;td>Encrypt and protect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Replication&lt;/td>
&lt;td>Secure replication users&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>Strong MySQL security doesn’t come from one feature or one tool. It comes from layers working together. Hardened configuration. Tight, intentional privilege design. Encryption everywhere it makes sense. And monitoring that actually gets reviewed instead of just written to disk.&lt;/p>
&lt;p>In my experience, the strongest environments aren’t the ones trying to be unbreakable. They’re the ones built to detect, contain, and respond. Every layer should either reduce blast radius or increase visibility. If an attacker gets through one control, the next one slows them down. And while they’re slowing down, your logging and monitoring should already be telling you something isn’t right.&lt;/p>
&lt;p>That’s what a mature security posture looks like in practice.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Percona</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>security</category><category>auditing</category><media:thumbnail url="https://percona.community/blog/2026/03/mysql-security_hu_2e3482c9b216e342.jpg"/><media:content url="https://percona.community/blog/2026/03/mysql-security_hu_5d7a0d72bf4766b.jpg" medium="image"/></item><item><title>A reponsible role for AI in Open Source projects?</title><link>https://percona.community/blog/2026/02/26/responsible-ai-for-oss/</link><guid>https://percona.community/blog/2026/02/26/responsible-ai-for-oss/</guid><pubDate>Thu, 26 Feb 2026 14:00:00 UTC</pubDate><description>AI-driven pressure on open source maintainers, reviewers and, even, contributors, has been very much in the news lately. Nobody needs another set of edited highlights on the theme from me. For a Postgres-specific view, and insight on how low quality AI outputs affect contributors, Tomas Vondra published a great post on his blog recently, which referenced an interesting talk by Robert Haas at PGConf.dev in Montreal last year. I won’t rehash the content here, they’re both quite quick reads and well worth the time.</description><content:encoded>&lt;p>AI-driven pressure on open source maintainers, reviewers and, even, contributors, has been very much in the news lately. Nobody needs another set of edited highlights on the theme from me. For a Postgres-specific view, and insight on how low quality AI outputs affect contributors, &lt;a href="https://vondra.me/posts/the-ai-inversion/" target="_blank" rel="noopener noreferrer">Tomas Vondra published a great post on his blog recently&lt;/a>, which referenced &lt;a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/254-committer-review-an-exercise-in-paranoia/" target="_blank" rel="noopener noreferrer">an interesting talk by Robert Haas&lt;/a> at PGConf.dev in Montreal last year. I won’t rehash the content here, they’re both quite quick reads and well worth the time.&lt;/p>
&lt;p>The key points which got me thinking about the rest of this piece are:&lt;/p>
&lt;ul>
&lt;li>Low quality AI reviews are also a time sink, not just AI slop patches&lt;/li>
&lt;li>AI is best at code review, so that happens soonest, but conceptual review should happen first&lt;/li>
&lt;/ul>
&lt;p>Today also marks the release of an out-of-cycle of Postgres. The release is happening because a number of security fixes which were introduced in the releases two weeks ago were found to cause functional and performance regressions. On the upside, this shows that the longer, public patch review process does find issues which otherwise go unnoticed. On the whole, fewer out-of-cycle releases, with the friction they create for the maintainers and users would, however, be better.&lt;/p>
&lt;p>The link between these two tracks of thought is review. Reviewing the security patches included in any Postgres release cycle is clearly different. What if it’s different in a way which suited AI assistance?&lt;/p>
&lt;p>We know that AI coding agents can contribute to this process, because one just has. Unfortunately, a little bit late. Some of the issues fixed in today’s out-of-cycle release were identified when a contributor opened up his agentic AI coding assistant one morning and asked it to tell him about the new patches. Some of those patches were newly visible to most contributors, after being pushed to the Git source control system for Postgres, because they were security patches which had not been discussed on the public mailing lists. You may hear the issues which drove these fixes referred to by the initialism for the Common Vulnerabilities and Exposures program which records these security reports (CVEs).&lt;/p>
&lt;p>It should go without saying that this is not just a case of: download Claude Code or Gemini Code Assist, install, add value and attain glory. There is definitely work involved in getting value out of these tools:
Setting up a project specific context is important, particularly for a long standing project like Postgres, which has some long standing coding styles
Asking the right question, known in the AI-assisted paradigm as prompting, will also require quite some understanding of the problem domain
Human judgement on the quality of the output. At its most basic, this is a decision about whether to turn the coding assistant’s output into a review, bin the output, or investigate further.&lt;/p>
&lt;p>The first two of those, the purely technical ones, are very unlikely to happen on the first attempt to use agentic AI as a coding assistant. This is a new class of tool everyone needs to learn. Perversely, people who have more of that third quality, that judgement, are more likely to walk away from their early experiments because those experiments are not delivering value. People skipping the third area of work is how we have ended up with AI being regarded as a burden on open source projects. This isn’t new, long before we got Large Language Models involved in code analysis, there were deterministic static analysis tools, and their false positives have been heaping noise on the signal in patch review processes for years.&lt;/p>
&lt;p>In his presentation which I linked above, Robert lists four key, high level principles for patch review:&lt;/p>
&lt;ul>
&lt;li>Start With The Big Picture&lt;/li>
&lt;li>Consistency Is Critical&lt;/li>
&lt;li>Be a Pessimist&lt;/li>
&lt;li>Apply a Spirit of Maintainership&lt;/li>
&lt;/ul>
&lt;p>The middle two of those principles, consistency and pessimism, are the two most amenable to AI interventions, and the two most important for reviewing security patches.&lt;/p>
&lt;p>When a piece of code has been committed, the big picture decisions and the decisions in the spirit of maintainership have already been made. If the code is later found to expose Postgres users to security issues, reviewing the solution comes down to all the possible angles of pessimism about the new code’s possible impact. It is in this area where Claude Code not just identified a regression in the case above, but also created a small reproducer to trigger the regression.&lt;/p>
&lt;p>Crafting and reviewing security patches is a beast of a task. The leading experts on all matters Postgres are writing and reviewing these patches, but the group is small, because any information about potential exploits needs to be kept under tight control. Timelines for a fix might also be quite tight. Both factors limit opportunities for collaboration and multi-stage review. In these conditions, an extra pair of AI eyes (A-Eyes?) on the patches may just be very useful.&lt;/p>
&lt;p>Set up, prompted, and reviewed by those who really know the problem domain, AI coding agents could provide valuable pointers on issues and regressions to consider when reviewing security patches. In that small bubble, this would not be a source of empty workload, but a source of extra review without having to coordinate more people in a small team. AI assistance will probably not provide time savings or quality gains on the first security patch it’s used to review, maybe not even on the seventh. After some setup effort, and a human learning a bit about improving its setup, an Agentic AI assistant (or two, with different configuration on different providers) may well be able to avoid another out-of-cycle release. That would be a major win.&lt;/p></content:encoded><author>Alastair Turner</author><category>PostgreSQL</category><category>Opensource</category><category>pg_alastair</category><category>Community</category><category>AI</category><media:thumbnail url="https://percona.community/blog/2026/02/OutOfCycleRelease_GeminiNanoBananaProImage_hu_1d2078b39a85ae5.jpg"/><media:content url="https://percona.community/blog/2026/02/OutOfCycleRelease_GeminiNanoBananaProImage_hu_af1faa463171a6a1.jpg" medium="image"/></item><item><title>Meet Percona at KubeCon + CloudNativeCon Europe 2026</title><link>https://percona.community/blog/2026/02/25/meet-percona-at-kubecon--cloudnativecon-europe-2026/</link><guid>https://percona.community/blog/2026/02/25/meet-percona-at-kubecon--cloudnativecon-europe-2026/</guid><pubDate>Wed, 25 Feb 2026 12:00:00 UTC</pubDate><description>The Percona team is heading to KubeCon + CloudNativeCon Europe in Amsterdam, and we’d love to meet you in person!</description><content:encoded>&lt;p>The Percona team is heading to KubeCon + CloudNativeCon Europe in Amsterdam, and we’d love to meet you in person!&lt;/p>
&lt;p>You can find us at &lt;strong>Booth 790&lt;/strong>. This is a great chance to talk with engineers working on Percona Operators.&lt;/p>
&lt;p>We will be there to discuss:&lt;/p>
&lt;ul>
&lt;li>Running MySQL, PostgreSQL, and MongoDB on Kubernetes&lt;/li>
&lt;li>Production-ready HA setups&lt;/li>
&lt;li>Backup and PITR strategies&lt;/li>
&lt;li>Multi-cluster and multi-region deployments&lt;/li>
&lt;li>Operators roadmap and upcoming features&lt;/li>
&lt;li>Real-world troubleshooting stories&lt;/li>
&lt;/ul>
&lt;p>If you’re running Percona Operators in production (or just getting started), we’d love to hear your feedback and learn about your challenges.&lt;/p>
&lt;p>If you’re just curious (or even suspicious) about running databases on Kubernetes, we’d love to talk and answer your questions.&lt;/p>
&lt;h3 id="admission-tickets---20-off-for-our-community">Admission Tickets - 20% Off for Our Community&lt;/h3>
&lt;p>We have a 20% discount code available for Percona community members.&lt;br>
If you’re planning to attend and don’t have a ticket yet, drop a comment or message us and we’ll share the details.&lt;/p>
&lt;h3 id="schedule-a-meeting">Schedule a Meeting&lt;/h3>
&lt;p>Want dedicated time with our engineers?&lt;br>
Drop a comment here or reach out directly. We’re happy to schedule a meeting during the event.&lt;/p>
&lt;p>See you in Amsterdam!&lt;/p></content:encoded><author>Ege Güneş</author><category>cloud</category><category>kubernetes</category><category>events</category><category>operators</category><media:thumbnail url="https://percona.community/blog/2026/02/kubecon_hu_5a17e098a1ce4fe3.jpg"/><media:content url="https://percona.community/blog/2026/02/kubecon_hu_3ed257b90767ff50.jpg" medium="image"/></item><item><title>PostgreSQL coffee break: version upgrade related reindexing - reasons</title><link>https://percona.community/blog/2026/02/25/postgresql-coffee-break-version-upgrade-related-reindexing-reasons/</link><guid>https://percona.community/blog/2026/02/25/postgresql-coffee-break-version-upgrade-related-reindexing-reasons/</guid><pubDate>Wed, 25 Feb 2026 11:00:00 UTC</pubDate><description>During FOSDEM I had a chance to join a presentation by Alexander Sosna of GitLab on the backup procedure he has followed with his team to decrease the downtime during major upgrades. I highly recommend the talk, definitely worth watching!</description><content:encoded>&lt;p>During FOSDEM I had a chance to join a &lt;a href="https://fosdem.org/2026/schedule/event/ZF8ZLX-zero-downtime-postgresql-upgrades/" target="_blank" rel="noopener noreferrer">presentation&lt;/a> by &lt;a href="https://www.linkedin.com/in/alexander-sosna-7193688b/" target="_blank" rel="noopener noreferrer">Alexander Sosna&lt;/a> of GitLab on the backup procedure he has followed with his team to decrease the downtime during major upgrades. I highly recommend the talk, definitely worth watching!&lt;/p>
&lt;p>I loved the presentation, especially since a similar procedure is what we recommend our users as well. Unfortunately it’s not for everyone: it’s applicable ONLY when you can stop DDL operations on your database cluster for some time. As you can imagine that’s not a case for every deployment.&lt;/p>
&lt;p>This limitation got me into some very engaging discussions during my fav part of any conference, the so called “hallway track”. Networking and discussions after the talks and on the conference corridors are why I like going to such events. I’ve heard some stories of nasty surprises coming out of the unexpected re-indexing after an upgrade.&lt;/p>
&lt;p>It made me wonder, how many professionals have moved to PostgreSQL from other areas of expertise and are lacking information about the index rebuilds after upgrades may be necessary. How often are DevOps engineers or SREs, neither fluent in PostgreSQL nor experienced with databases, tasked with maintaining database infrastructure?&lt;/p>
&lt;p>In the meantime I figured out that a short coffee time read is what I want to aim at. No super deep dives, rather food for thought and inspiring more of those engaging discussions I like so much about the conferences.&lt;/p>
&lt;p>So let’s get to it. This week I want to go through some basic facts before diving into more challenging topics in the coming weeks.&lt;/p>
&lt;h3 id="what-are-collations">What are collations&lt;/h3>
&lt;p>In general, a collation defines how values are ordered and compared. In databases, it most commonly applies to text. It’s often not a trivial thing to determine how strings are sorted, whether two values are considered equal, and how things like upper vs lower case or special characters are treated. Just like alphabetical order helps us organize words in everyday life, collations define the rules the database uses when comparing and sorting character data. This allows us to sort structures like strings. Numbers are even simpler.&lt;/p>
&lt;p>A good visualization of how significant collations are is when you try to imagine determining whether one item is before another if they come from different alphabets. Where do you position country specific characters in such a sorting order?&lt;/p>
&lt;p>Lets look at a set of example data:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Bob
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anna
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Zoë
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Álvaro&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When using an English-like collation the sorting would look like this as &lt;code>Á&lt;/code> is treated like &lt;code>A&lt;/code> . This is visible in Collation 1:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Anna
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Álvaro
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Bob
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Zoë&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Though with a collation changed so that &lt;code>Á&lt;/code> is sorted separately before &lt;code>A&lt;/code> . This is visible in Collation 2:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Álvaro
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anna
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Bob
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Zoë&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It’s clearly visible that collation defines how text is sorted and compared.&lt;/p>
&lt;h3 id="what-are-indexes">What are indexes&lt;/h3>
&lt;p>If you’re not a database professional, you may not be familiar with what an index is. Think of it as of a structure that speeds up the search. In an old school library you had to check the index of authors to find the book you’ve been looking for. Similarly in databases when knowing what the data is going to be searched for, we introduce indexes.&lt;/p>
&lt;p>A common use of indexes is to enforce uniqueness of values. Primary keys and unique constraints automatically create unique indexes to ensure that no duplicate values are inserted. In addition, users can create their own indexes to support specific query patterns. Indexes can be single or multi column, depending on the particular use cases and to help speed up specific queries.
Let’s look at an example of physically unsorted data stored in heap&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Users (unsorted data)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[1] Zoë
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[2] Álvaro
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[3] Anna
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[4] Bob&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>with an Index on name&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Index (B-tree on name)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">----------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anna -> [3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Bob -> [4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Zoë -> [1]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Álvaro -> [2]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The index stores values in sorted order and points to their location in the table. Scanning through the list in order is fine for small number of items, as indexes get larger, there are opportunities to optimize this process. The most commonly used optimisation, very simplified, is to split the list and create a pointer which stores the maximum value in the left half of the list and the minimum value in the right half. As the lists get too large again, they are split again, creating a tree of these pointers.&lt;/p>
&lt;h3 id="why-re-indexing-is-needed">Why re-indexing is needed&lt;/h3>
&lt;p>If the rules for comparing the strings are different at query time from what they were when the index was created, the search may fail in various ways. If the scan encounters a value which, by the rules at query time, is higher in the sort order than the value being searched for, it will conclude that the value being searched for is not in the list. Similarly, the search may follow a pointer to the wrong list of values. To make this more concrete, let’s look at a &lt;a href="https://lists.debian.org/debian-glibc/2019/03/msg00030.html" target="_blank" rel="noopener noreferrer">real world example&lt;/a> from a &lt;code>glibc&lt;/code> change in 2019. Before the change, a list of our values was sorted as follows&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">aa
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a-a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a+a&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>after the change, the list was sorted as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">a a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a+a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a-a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aa&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If an index was built under the first set of rules and searched under the second, a search for the value ‘a a’ will fail, because the first item in the index (‘aa’) is higher in the sort order than the value being searched for. Failing to find a value which is in the list can cause issues with application behavior - like missing records or records which appear in some queries (when the index is not used) but not in others (where the index is used). It may allow the insertion of values that should now be considered equal under the new collation rules, effectively violating uniqueness expectations.&lt;/p>
&lt;p>Since PostgreSQL 10, the server tracks the collation version used when an index was built and can detect when it no longer matches the system’s current collation version, which is why reindexing may suddenly become mandatory after an upgrade.&lt;/p>
&lt;p>However, PostgreSQL server does not analyze your actual data to determine whether the collation change affects your stored values. It only detects that the collation provider version has changed. In some cases, this means reindexing is required even though the effective sort order of your specific data has not changed. Because PostgreSQL cannot reliably determine whether your specific dataset is affected, it treats the situation as potentially unsafe.&lt;/p>
&lt;h3 id="when-do-collations-change">When do collations change?&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_6fad21a66cb609b1.webp 768w, https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_532a380cef2b3956.webp 480w, https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_8bf34db7fd20efb5.webp 1400w" src="https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_8bf34db7fd20efb5.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>There’s a number of scenarios when collations change:&lt;/p>
&lt;ul>
&lt;li>OS / &lt;code>glibc&lt;/code> upgrade (Linux) - PostgreSQL relies on the system &lt;code>glibc&lt;/code> provided collations so if a version changes, the collation rules may change as well. The example above is &lt;a href="https://lists.debian.org/debian-glibc/2019/03/msg00030.html" target="_blank" rel="noopener noreferrer">taken from the upgrade to glibc 2.28&lt;/a> which is one &lt;a href="https://wiki.postgresql.org/wiki/Locale_data_changes" target="_blank" rel="noopener noreferrer">PostgreSQL Community remembers&lt;/a> due to the ripple effect it caused.&lt;/li>
&lt;li>ICU library upgrade (for ICU collations) - if PostgreSQL deployment uses ICU collations, upgrading ICU library will affect these. While they are not tied to &lt;code>glibc&lt;/code> these changes also happen.&lt;/li>
&lt;li>Major PostgreSQL upgrade (in some cases) - if the new version uses a different collation provider behavior or updated ICU integration&lt;/li>
&lt;li>Database restored on a system with different collation versions - logical dump/restore onto a host with different &lt;code>glibc&lt;/code> / ICU versions can invalidate indexes.&lt;/li>
&lt;/ul>
&lt;h3 id="when-re-indexing-is-necessary">When re-indexing is necessary&lt;/h3>
&lt;p>Looking at the above provided list an observation is quite immediate, that not every collation type is affected:&lt;/p>
&lt;ul>
&lt;li>if the collation is not dependent on &lt;code>glibc&lt;/code> / ICU then it won’t be affected. As such C/POSIX collations are immune to such issues.&lt;/li>
&lt;li>Same truth sticks to the data types. The collation change problem affects only those indexes which are the text or character based. All other datatypes like all types of integers and floating points, date, timestamp,  geometric data types or even vector data remain unaffected.&lt;/li>
&lt;/ul>
&lt;p>What’s also important is that even if a collation changed it’s not necessary it will affect a given index. Think of it this way, if your database does not use any language specific characters, chances are that collation change will not require re-index. Unfortunately you don’t know that until you look inside your data.
In controlled environments, teams may assess the risk before scheduling reindexing, but from PostgreSQL’s perspective the index must be considered potentially inconsistent until rebuilt.&lt;/p>
&lt;h3 id="whats-next">What’s next?&lt;/h3>
&lt;p>Next week we’ll look at what is the reality of the DBA team regarding upgrades&lt;/p></content:encoded><author>Jan Wieremjewicz</author><author>Alastair Turner</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_alastair</category><media:thumbnail url="https://percona.community/blog/2026/02/Jan-Cover-Feb25_hu_fbc35474e234e97b.jpg"/><media:content url="https://percona.community/blog/2026/02/Jan-Cover-Feb25_hu_b34d7bdf1c4cd9a6.jpg" medium="image"/></item><item><title>PostgreSQL minor release postponed in Q1’ 2026</title><link>https://percona.community/blog/2026/02/18/postgresql-minor-release-postponed-in-q1-2026/</link><guid>https://percona.community/blog/2026/02/18/postgresql-minor-release-postponed-in-q1-2026/</guid><pubDate>Wed, 18 Feb 2026 11:00:00 UTC</pubDate><description>In case you are awaiting the February PostgreSQL Community minor update released on plan on February 12 we want to make sure that our users and customers are up to date and aware of what to expect.</description><content:encoded>&lt;p>In case you are awaiting the February PostgreSQL Community minor update &lt;a href="https://www.postgresql.org/about/news/postgresql-182-178-1612-1516-and-1421-released-3235/" target="_blank" rel="noopener noreferrer">released on plan on February 12&lt;/a> we want to make sure that our users and customers are up to date and aware of what to expect.&lt;/p>
&lt;p>This scheduled PostgreSQL release was delivered by the PostgreSQL Community on time and came carrying 5 CVE fixes and over 65 bugs bug fixes.&lt;/p>
&lt;p>Unfortunately shortly after, the &lt;a href="https://www.postgresql.org/about/news/out-of-cycle-release-scheduled-for-february-26-2026-3241/" target="_blank" rel="noopener noreferrer">release team announced that an additional out of cycle release&lt;/a> is planned for February 26. This follow up release addresses two regressions identified in the February 12 update.&lt;/p>
&lt;p>Because of this, we have decided not to ship a &lt;a href="https://docs.percona.com/postgresql/18/" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> build based on the February 12 release. Instead, we will wait for the February 26 Community update and base our release on that version once it becomes available from PGDG. This means also a delay in the release of Percona Operator for PostgreSQL that uses images based on our PostgreSQL releases.&lt;/p>
&lt;h3 id="always-look-on-the-bright-side">Always look on the bright side&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/Jan-always-Feb17_hu_9d06394a379f29f1.webp 768w, https://percona.community/blog/2026/02/Jan-always-Feb17_hu_fc5ff229bb22cbc.webp 480w, https://percona.community/blog/2026/02/Jan-always-Feb17_hu_cee9f5c7742b62d4.webp 1400w" src="https://percona.community/blog/2026/02/Jan-always-Feb17_hu_cee9f5c7742b62d4.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>While this is a delay in release it comes with some benefits. For our users and customers, this means a cleaner upgrade path. Rather than releasing February 12 now and asking you to update again shortly after, we prefer to wait and deliver a single update that includes the fixes. Our goal is to make updates predictable and smooth for users of Percona Distribution for PostgreSQL, as well as extensions such as &lt;a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">pg_tde&lt;/a> and &lt;a href="https://github.com/percona/pg_stat_monitor" target="_blank" rel="noopener noreferrer">pg_stat_monitor&lt;/a>. It should also allow you to carry less operational burden with the added maintenance that an extra update would require.&lt;/p>
&lt;p>We appreciate how quickly the PostgreSQL Community identified and addressed the regressions. Open collaboration across the ecosystem, including reports and testing from many contributors, helps ensure PostgreSQL continues to improve for everyone.&lt;/p>
&lt;h3 id="path-forward">Path forward&lt;/h3>
&lt;p>This is the third out of cycle release in the past year, following similar updates in &lt;a href="https://www.postgresql.org/about/news/out-of-cycle-release-scheduled-for-november-21-2024-2958/" target="_blank" rel="noopener noreferrer">November 2024&lt;/a> and &lt;a href="https://www.postgresql.org/about/news/out-of-cycle-release-scheduled-for-february-20-2025-3016/" target="_blank" rel="noopener noreferrer">February 2025&lt;/a>. It highlights how responsive and diligent the PostgreSQL Community is when issues are identified. At the same time, it reminds us all how important continuous testing and collaboration are as PostgreSQL adoption continues to grow. Contributing to PostgreSQL, whether through testing, reporting, or development, is one of the best ways to help strengthen quality across the ecosystem.&lt;/p>
&lt;h3 id="elephants-keep-ears-open">Elephants keep ears open&lt;/h3>
&lt;p>As soon as the February 26 release is available and our builds are ready, we will share the update.&lt;/p>
&lt;p>If you need to move forward with the February 12 version in the meantime, please reach out. We are happy to talk through your situation and help you assess what makes the most sense for your environment.&lt;/p>
&lt;p>Our customers can contact us through Percona Support Services to receive the high quality assistance we are known for. We also encourage community users to reach out via the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a>, where we will do our best to provide guidance based on the information you share.&lt;/p>
&lt;p>Thank you for your trust and for being part of the Percona community.&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2026/02/Jan-Cover-Feb17_hu_bb8d48d425327c09.jpg"/><media:content url="https://percona.community/blog/2026/02/Jan-Cover-Feb17_hu_3fc4aada7c2afd7b.jpg" medium="image"/></item><item><title>Pre-FOSDEM &amp; FOSDEM 2026, Community, Databases, and Open Source</title><link>https://percona.community/blog/2026/02/09/pre-fosdem-fosdem-2026-community-databases-and-open-source/</link><guid>https://percona.community/blog/2026/02/09/pre-fosdem-fosdem-2026-community-databases-and-open-source/</guid><pubDate>Mon, 09 Feb 2026 10:00:00 UTC</pubDate><description>This is a recap of Percona at preFosdem and Fosdem!</description><content:encoded>&lt;p>This is a recap of Percona at preFosdem and Fosdem!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1920" height="1080" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-all_hu_763a9595fdd9602d.webp 768w, https://percona.community/blog/2026/02/fosdem-all_hu_cecebe3af1478fc4.webp 480w, https://percona.community/blog/2026/02/fosdem-all_hu_7d06c4b9eb5e1305.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-all_hu_7d06c4b9eb5e1305.webp" alt="Fosdem intro" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Before FOSDEM officially started, the database community gathered for MySQL Belgium Days (Pre-FOSDEM), a two-day event bringing together MySQL developers, DBAs, engineers, tool builders, and open-source enthusiasts. It was an excellent space for deep technical discussions, knowledge sharing, and reconnecting with the community, hosted by the amazing &lt;strong>Frederic Descamps&lt;/strong>.
The event featured strong participation from &lt;strong>Percona&lt;/strong> and the wider MySQL ecosystem, with talks led by &lt;strong>Peter Zaitsev, Marco Tusa, Fernando Laudares Camargos, Arunjith Aravindan, Vinicius Grippa, Pep Pla, and Yura Sorokin&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1317" height="616" sizes="(max-width: 1317px) 100vw, 1317px" srcset="https://percona.community/blog/2026/02/fosdem-speakers_hu_938c910cab66e5de.webp 768w, https://percona.community/blog/2026/02/fosdem-speakers_hu_d29e8a1373036b5.webp 480w, https://percona.community/blog/2026/02/fosdem-speakers_hu_923d235a9c77b1.webp 1317w" src="https://percona.community/blog/2026/02/fosdem-speakers_hu_923d235a9c77b1.webp" alt="Fosdem speakers" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Find the recordings of the talks &lt;a href="https://www.youtube.com/playlist?list=PL6tzEWmw-bpxe0k5Xrk09N-m6q5rGTy_l" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;p>Also, in Belgium same week, several other events took place, including &lt;strong>PGDay-FOSDEM&lt;/strong>, &lt;strong>MariaDB Day&lt;/strong>, and the &lt;strong>MySQL Summit&lt;/strong>.&lt;/p>
&lt;p>At &lt;strong>PGDay&lt;/strong>, it was a pleasure to see the PostgreSQL community together; we had several participants representing us.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1172" height="449" sizes="(max-width: 1172px) 100vw, 1172px" srcset="https://percona.community/blog/2026/02/fosdem-pg_hu_f492be895545a1d3.webp 768w, https://percona.community/blog/2026/02/fosdem-pg_hu_45cd62e423c6ee6.webp 480w, https://percona.community/blog/2026/02/fosdem-pg_hu_f591cc8b94186875.webp 1172w" src="https://percona.community/blog/2026/02/fosdem-pg_hu_f591cc8b94186875.webp" alt="Fosdem speakers" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>MariaDB Day&lt;/strong>. We had Peter Zeitsev presenting a talk titled “What MariaDB Community can learn from PostgreSQL?”
&lt;figure>&lt;img width="4032" height="2268" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-peter_hu_33800a9545943417.webp 768w, https://percona.community/blog/2026/02/fosdem-peter_hu_8a95e6e03cb05243.webp 480w, https://percona.community/blog/2026/02/fosdem-peter_hu_d39c99ad9e52489c.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-peter_hu_d39c99ad9e52489c.webp" alt="Fosdem speakers" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="mysql-summit">MySQL summit&lt;/h2>
&lt;p>During MySQL Days in Brussels, the community gathered for an in-person MySQL Summit focused on collaboration and strengthening the MySQL ecosystem, with open discussions around its present and future driven by community involvement.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="2268" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_ef43450b7ff39945.webp 768w, https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_cf6126803c6162be.webp 480w, https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_e6fbcbced6cd724e.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_e6fbcbced6cd724e.webp" alt="Fosdem MySQL Summit" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="mysql-rockstars-2026">MySQL RockStars 2026&lt;/h2>
&lt;p>The MySQL Rockstar Award is a recognition given by the MySQL Community Team at Oracle, together with previous award winners, to members of the MySQL community who have actively contributed to promoting MySQL during the past year.
MySQL Legends are long-standing community members who have made a significant and lasting impact on the adoption, development, and evolution of MySQL over many years.&lt;/p>
&lt;p>This year, the MySQL RockStars selected were:&lt;/p>
&lt;ul>
&lt;li>Matthias Crauwels&lt;/li>
&lt;li>Marco Tusa&lt;/li>
&lt;li>Umesh Shastry&lt;/li>
&lt;li>Ronald Bradford&lt;/li>
&lt;li>Marcelo Altmann&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1600" height="1200" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-rockstars_hu_4e5f25c994562d09.webp 768w, https://percona.community/blog/2026/02/fosdem-rockstars_hu_53196fb9fc36ca07.webp 480w, https://percona.community/blog/2026/02/fosdem-rockstars_hu_8433bff1f6445f59.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-rockstars_hu_8433bff1f6445f59.webp" alt="Fosdem postgressql" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Congratulations to all of them! You can find previous &lt;a href="https://www.mysqlandfriends.eu/mysql-rockstars-hall-of-fame/" target="_blank" rel="noopener noreferrer">MySQL RockStars in this list&lt;/a>&lt;/p>
&lt;h2 id="fosdem-2026-percona-booth">FOSDEM 2026, Percona booth&lt;/h2>
&lt;p>At the Percona booth, conversations focused on open-source databases and Kubernetes, including &lt;a href="https://github.com/openeverest/openeverest" target="_blank" rel="noopener noreferrer">OpenEverest’s&lt;/a> first-ever presence at FOSDEM. Around 40 Perconians were present, a great chance to finally meet many colleagues in person. As always, we had many visitors, and it was great to see that many already knew about Percona, while others were eager to learn more and explore what we do in the world of Open Source!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1090" height="473" sizes="(max-width: 1090px) 100vw, 1090px" srcset="https://percona.community/blog/2026/02/fosdem-booth_hu_16f4f70db71c7b22.webp 768w, https://percona.community/blog/2026/02/fosdem-booth_hu_f0bfaf196a46fb8e.webp 480w, https://percona.community/blog/2026/02/fosdem-booth_hu_5f5d91e5bc3909da.webp 1090w" src="https://percona.community/blog/2026/02/fosdem-booth_hu_5f5d91e5bc3909da.webp" alt="Fosdem postgressql" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="fosdem-2026-databases-devroom">FOSDEM 2026, Databases DevRoom&lt;/h2>
&lt;p>FOSDEM 2026 officially kicked off at the ULB Solbosch Campus, bringing together thousands of open-source contributors from around the world.
The Database DevRoom (UB2.252A) was packed with high-quality talks and discussions, co-led with &lt;strong>Matthias Crauwels&lt;/strong> and &lt;strong>Ray Paik&lt;/strong>, and&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2048" height="1536" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-database-01_hu_456364c36e170a40.webp 768w, https://percona.community/blog/2026/02/fosdem-database-01_hu_83db1aba3bec0eca.webp 480w, https://percona.community/blog/2026/02/fosdem-database-01_hu_e80567875d768fa8.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-database-01_hu_e80567875d768fa8.webp" alt="Fosdem postgressql" loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="2048" height="1536" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-database-02_hu_65789e11c976f463.webp 768w, https://percona.community/blog/2026/02/fosdem-database-02_hu_88fc5325fa7e43d4.webp 480w, https://percona.community/blog/2026/02/fosdem-database-02_hu_9a03338ad8fd9329.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-database-02_hu_9a03338ad8fd9329.webp" alt="Fosdem postgressql" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Find more details and some of the recordings &lt;a href="https://fosdem.org/2026/schedule/track/databases/" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h2 id="celebrating-20-years-of-percona">Celebrating 20 Years of Percona&lt;/h2>
&lt;p>This year, &lt;strong>Percona&lt;/strong> celebrates its 20th anniversary. Throughout FOSDEM and Pre-FOSDEM events, it was inspiring to meet long-time users who have relied on Percona’s open-source solutions for years and shared their positive experiences.
You can explore Percona’s 20-year journey here:
👉 &lt;a href="https://percona20.com/" target="_blank" rel="noopener noreferrer">https://percona20.com/&lt;/a>
If you’ve had a great experience with Percona, you’re invited to share your story via the community survey.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1167" height="825" sizes="(max-width: 1167px) 100vw, 1167px" srcset="https://percona.community/blog/2026/02/fosdem-percona-20_hu_b48ee32f1d7f7165.webp 768w, https://percona.community/blog/2026/02/fosdem-percona-20_hu_a19d3208e77a1f14.webp 480w, https://percona.community/blog/2026/02/fosdem-percona-20_hu_7131f2ee164644a.webp 1167w" src="https://percona.community/blog/2026/02/fosdem-percona-20_hu_7131f2ee164644a.webp" alt="Fosdem postgressql" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>See you at FOSDEM 2027!&lt;/p></content:encoded><author>Edith Puclla</author><category>Percona</category><category>PostgreSQL</category><category>Community</category><category>Events</category><category>FOSDEM</category><media:thumbnail url="https://percona.community/blog/2026/02/fosdem-all_hu_ead0e789549fbf3a.jpg"/><media:content url="https://percona.community/blog/2026/02/fosdem-all_hu_f5de724365851f2d.jpg" medium="image"/></item><item><title>PGDay and FOSDEM Report from Kai</title><link>https://percona.community/blog/2026/02/04/pgday-and-fosdem-report-from-kai/</link><guid>https://percona.community/blog/2026/02/04/pgday-and-fosdem-report-from-kai/</guid><pubDate>Wed, 04 Feb 2026 10:00:00 UTC</pubDate><description>The following thoughts and comments are completely my personal opinion and do not reflect my employers thoughts or beliefs. If you don’t like anything in this post, reach out to me directly, so I can ignore it ;-).</description><content:encoded>&lt;p>The following thoughts and comments are completely my personal opinion and do not reflect my employers thoughts or beliefs. If you don’t like anything in this post, reach out to me directly, so I can ignore it ;-).&lt;/p>
&lt;p>I’m currently on the train on my way back home from FOSDEM this year and man, I’m exhausted but also happy. Why? Because the PG and FOSDEM community is just crazily awesome. While it’s always too much of everything, it’s at the same time inspiring to see so many enthusiastic IT nerds in one place, discussing and working on what they love - technology and engineering challenges.&lt;/p>
&lt;h2 id="pgday-fosdem">PGDay FOSDEM&lt;/h2>
&lt;p>It all started with the usual PGDay FOSDEM the day before FOSDEM. Just in case - this has been happening for over 15 years and if you read this as a little blame that you didn’t know about it, that’s absolutely correct, as you should. It’s been a great event as usual: around 150 Postgres enthusiasts collaborating with each other. There was a great set of talks (no recording available, so yes, just join next year to not miss anything), as well as the hallway track conversations.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1542" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/pgday-slonik_hu_9c2796c3635d1bc.webp 768w, https://percona.community/blog/2026/02/pgday-slonik_hu_27f59bb770c15455.webp 480w, https://percona.community/blog/2026/02/pgday-slonik_hu_f2e4f0e89ddaa055.webp 1400w" src="https://percona.community/blog/2026/02/pgday-slonik_hu_f2e4f0e89ddaa055.webp" alt="PGDay Kai and Slonik" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I was able and accepted again as a volunteer helping to make the event happen. While you might think, what’s special about it, I cannot express my gratitude for being able to help in any way. I simply love it. I’m not a great coder and I’ve never been one. I’m the one that looks at his code from a year ago and questions his technical existence and overall abilities if I should rather do something without touching a keyboard. What I am very well capable of is helping and supporting events. So it was my pleasure and I hope you do feel inspired to do the same next year or at any future event, not only in the Postgres ecosystem but in general. I strongly believe in this: doing good things will get you good things back.&lt;/p>
&lt;p>After the wrap up to the PGDay and a great community dinner to collaborate and discuss further, I simply fell completely tired asleep, as the next day and FOSDEM was already waiting.&lt;/p>
&lt;h2 id="fosdem-day-1">FOSDEM Day 1&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="2268" height="4032" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_36187e7fa4a76aad.webp 768w, https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_c99a105b716df5e.webp 480w, https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_b97f333c090b19b7.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_b97f333c090b19b7.webp" alt="PGDay Kai PG Booth Volunteering" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The next day started with volunteering at the Postgres booth. As usual, Saturday was simply crazy. The Postgres swag like hoodies, caps, mugs, shirts, etc. was almost ripped out of our living hands. We had people waiting in line just to be able to get some swag. That fact alone shows how Postgres is viewed outside of the internal PG ecosystem community. How many times I heard the sentence “Thanks a lot for the great work you do” or “Postgres just works.” Yeah, we can all argue about the details and scenarios, but what this is about is the overall ease of use. Not everyone has terabytes of data or the most complex HA and replication scenarios on this planet. Some just need a functional and boring database and, in the best case, open source - and we all know, looking at real open source, not single-vendor owned, Postgres is the king and here to stay.&lt;/p>
&lt;p>After all of this, I switched clothes and helped at the Percona booth. This wasn’t any less interesting in comparison to the PG booth. How many people stepped by, asking about what we do or thanking us for our projects and that we remained open source even after all these years and so many other companies not withstanding the quick and easy money to go with open-core or closed offerings. That’s the reason I’m proud to be part of this company. We walk the talk, since 20y and we have no incentive ever changing it. Thanks to Peter Zaitsev and Peter Farkas aka P² - for those who know, just know.&lt;/p>
&lt;p>Following that I had the pleassure of being the Slonik guide again. What is a Slonik guide you might ask? Slonik, the mascot of Postgres (big blue elephant), needs some help and guidance while walking throught the crowd, as you can barely see anything while inside the costome. As usual, Slonik is a celebraty. Everyone wants a picture and taking their chance to photograph Slonik in the “wild”. As you can see, even MySQL’s Sakila couldn’t resist and had to take a picture with Slonik.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1542" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-slonik_hu_870f1802911e0aa7.webp 768w, https://percona.community/blog/2026/02/fosdem-slonik_hu_73f249bcf5415c56.webp 480w, https://percona.community/blog/2026/02/fosdem-slonik_hu_42e8ca0dcb376339.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-slonik_hu_42e8ca0dcb376339.webp" alt="FOSDEM Sakila and Slonik" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you’re wondering, like many others, why Slonik and why an Elephant? &lt;a href="https://learnsql.com/blog/the-history-of-slonik-the-postgresql-elephant-logo/" target="_blank" rel="noopener noreferrer">Click here for some nice written down history lesson&lt;/a>&lt;/p>
&lt;p>After an exciting but also energy-draining day, I enjoyed a Percona crew/team dinner at BrewDog, with some great conversations and good food. &lt;a href="https://www.reddit.com/r/Homebrewing/comments/47icau/brewdog_just_open_sourced_all_their_recipes/" target="_blank" rel="noopener noreferrer">Fun Fact: Did you know that BrewDog is also open source?&lt;/a>. I couldn’t stay too long - sorry about that - but I had another date. The famous Floor Drees organized in tradition another karaoke event that I couldn’t miss. As I couldn’t make it to earlier versions of it, I definitely wanted to join. What should I say apart from thanks, Floor, for this great tradition. Yes, I had a hard time talking the next day, but damn I had fun singing Swedish, Polish, German, and English songs - and yes, I most likely misunderstood all of them as usual.&lt;/p>
&lt;p>Too many songs for my voice and maybe a “soft drink or two” later, I felt in my bed like a stone, and couldn’t really accept the fact that my alarm clock went off almost five minutes later (at least that’s how it felt to me).&lt;/p>
&lt;h2 id="fosdem-day-2">FOSDEM Day 2&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1536" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_76ac9d03a9dd79c0.webp 768w, https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_7c8a5fae64efc860.webp 480w, https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_7d4c304f24a0936a.webp 1400w" src="https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_7d4c304f24a0936a.webp" alt="PGDay Kai PG Booth Volunteering" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>No whining helped, just getting up and making myself ready for Day 2 of FOSDEM, which started with another round of volunteering at the Postgres and Percona booths. Both basically matched the previous feedback, apart from a definitely dropped and less crowded space - seems I wasn’t the only one singing last night ;-).&lt;/p>
&lt;p>With that, thanks a lot to everyone making this great FOSDEM happen. I’ll try now if the Deutsche Bahn restaurant actually works this time, as I need coffee, a big one, maybe two… See all of you next year again or at another event this year.&lt;/p>
&lt;blockquote>
&lt;p>Stay on top of Postgres development without the inbox overwhelm. Explore &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">hackorum.dev&lt;/a> today and share your feedback with us.&lt;/p>&lt;/blockquote></content:encoded><author>Kai Wagner</author><category>Percona</category><category>PostgreSQL</category><category>Community</category><category>Events</category><category>FOSDEM</category><category>pg_kwagner</category><media:thumbnail url="https://percona.community/blog/2026/02/FOSDEM_logo.svg_hu_ae522c5838afabab.jpg"/><media:content url="https://percona.community/blog/2026/02/FOSDEM_logo.svg_hu_27fd666d652e3786.jpg" medium="image"/></item><item><title>Hackorum - A Forum-Style View of pg-hackers</title><link>https://percona.community/blog/2026/02/02/hackorum-a-forum-style-view-of-pg-hackers/</link><guid>https://percona.community/blog/2026/02/02/hackorum-a-forum-style-view-of-pg-hackers/</guid><pubDate>Mon, 02 Feb 2026 00:00:00 UTC</pubDate><description>Last year at pgconf.dev, there was a discussion about improving the user interface for the PostgreSQL hackers mailing list, which is the main communication channel for PostgreSQL core development. Based on that discussion, I want to share a small project we have been working on:</description><content:encoded>&lt;p>Last year at pgconf.dev, there was a discussion about improving the user interface for the PostgreSQL hackers mailing list, which is the main communication channel for PostgreSQL core development. Based on that discussion, I want to share a small project we have been working on:&lt;/p>
&lt;p>&lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">https://hackorum.dev/&lt;/a>&lt;/p>
&lt;p>Hackorum provides a &lt;strong>read-only (for now)&lt;/strong> web view of the mailing list with a more forum-like presentation. It is a &lt;strong>work-in-progress proof of concept&lt;/strong>, and we are primarily looking for feedback on whether this approach is useful and what we should improve next.&lt;/p>
&lt;h2 id="what-hackorum-already-does">What Hackorum already does&lt;/h2>
&lt;p>Hackorum focuses on readability, navigation, and workflow improvements for people who follow pg-hackers. Some highlights:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Continuous mailing list synchronization&lt;/strong>: The site is subscribed to the list&lt;/li>
&lt;li>&lt;strong>Commitfest integration&lt;/strong>: See commitfest context next to threads - you directly know what’s the state of this commit/thread.&lt;/li>
&lt;li>&lt;strong>User profiles&lt;/strong>: Contributor/committer status from the main website&lt;/li>
&lt;li>&lt;strong>Statistics&lt;/strong>: Per-user and mailing lists insights&lt;/li>
&lt;li>&lt;strong>Easy download of attached patches&lt;/strong>: Including helper script for easy rebase and merge&lt;/li>
&lt;li>&lt;strong>Additional logged-in user features&lt;/strong>: Per-message read status, starring threads, tags, notes, mentions on messages and threads&lt;/li>
&lt;li>&lt;strong>Basic team support&lt;/strong>: Shared reading status, shared mentioned tags and notes - mention someone underneath an email an the person gets notified&lt;/li>
&lt;li>&lt;strong>Resend email&lt;/strong>: Integration from the official archive&lt;/li>
&lt;li>&lt;strong>Importing read status / tags via CSV files&lt;/strong>: To help migration from email-based workflows&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="2728" height="1216" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/02/hackorum-topics_hu_69a374d1dd27383d.webp 768w, https://percona.community/blog/2026/02/hackorum-topics_hu_fbec770034f0dcd2.webp 480w, https://percona.community/blog/2026/02/hackorum-topics_hu_d07aed84ec43688a.webp 1400w" src="https://percona.community/blog/2026/02/hackorum-topics_hu_d07aed84ec43688a.webp" alt="Hackorum topics overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="what-we-plan-next">What we plan next&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Sending emails from the web UI&lt;/strong>: Initially via Gmail API for Google-authenticated users who authorize sending&lt;/li>
&lt;li>&lt;strong>Advanced search functionality&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Integrating other mailing lists&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="try-it-and-share-feedback">Try it and share feedback&lt;/h2>
&lt;p>If you want to take a look, just got to &lt;a href="https://hackorum.dev/" target="_blank" rel="noopener noreferrer">https://hackorum.dev/&lt;/a>&lt;/p>
&lt;p>The repository, including a simple dev setup, can be found here: &lt;a href="https://github.com/hackorum-dev/hackorum" target="_blank" rel="noopener noreferrer">https://github.com/hackorum-dev/hackorum&lt;/a>&lt;/p>
&lt;p>Is this useful? What is missing? What would you change? Bug reports, feature requests, and contributions are all welcome. &lt;a href="https://github.com/hackorum-dev/hackorum/issues" target="_blank" rel="noopener noreferrer">https://github.com/hackorum-dev/hackorum/issues&lt;/a>&lt;/p>
&lt;p>Thanks for taking a look, and we appreciate any feedback.&lt;/p></content:encoded><author>Kai Wagner</author><category>Percona</category><category>PostgreSQL</category><category>Community</category><category>Mailing Lists</category><category>Open Source</category><category>pg_kwagner</category><media:thumbnail url="https://percona.community/blog/2026/02/hackorum-banner_hu_a490f1e3fc7fc4de.jpg"/><media:content url="https://percona.community/blog/2026/02/hackorum-banner_hu_f4b317e9a789d5be.jpg" medium="image"/></item><item><title>Tuning MySQL for Performance: The Variables That Actually Matter</title><link>https://percona.community/blog/2026/02/01/tuning-mysql-for-performance-the-variables-that-actually-matter/</link><guid>https://percona.community/blog/2026/02/01/tuning-mysql-for-performance-the-variables-that-actually-matter/</guid><pubDate>Sun, 01 Feb 2026 00:00:00 UTC</pubDate><description>There is a special kind of boredom that only database people know. The kind where you stare at a server humming along and think, surely there is something here I can tune. Good news: there is.</description><content:encoded>&lt;p>There is a special kind of boredom that only database people know. The kind where you stare at a server humming along and think, &lt;em>surely there is something here I can tune&lt;/em>. Good news: there is.&lt;/p>
&lt;p>This post walks through the &lt;strong>most important MySQL variables to tune for performance&lt;/strong>, why they matter, and when touching them helps versus when it quietly makes things worse. This is written with &lt;strong>InnoDB-first workloads&lt;/strong> in mind, because let’s be honest, that’s almost everyone.&lt;/p>
&lt;hr>
&lt;h2 id="1-innodb_buffer_pool_size">1. &lt;code>innodb_buffer_pool_size&lt;/code>&lt;/h2>
&lt;h3 id="real-metrics-to-watch">Real metrics to watch&lt;/h3>
&lt;p>Before touching this variable, look at these:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_buffer_pool_read%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Key fields:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Innodb_buffer_pool_reads&lt;/code> – physical reads from disk&lt;/li>
&lt;li>&lt;code>Innodb_buffer_pool_read_requests&lt;/code> – logical reads&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Rule of thumb:&lt;/strong>
If &lt;code>reads / read_requests&lt;/code> > 1–2%, your buffer pool is too small.&lt;/p>
&lt;h3 id="example-graph">Example graph&lt;/h3>
&lt;p>Plot &lt;code>Innodb_buffer_pool_reads&lt;/code> over time. A healthy system shows a flat or gently rising line. Spikes that look like a city skyline usually mean memory pressure or a cold cache.&lt;/p>
&lt;p>If MySQL performance had a crown jewel, this would be it.&lt;/p>
&lt;h3 id="what-it-does">What it does&lt;/h3>
&lt;p>The InnoDB buffer pool caches table data and indexes in memory. Reads served from RAM are fast. Reads from disk are… character building.&lt;/p>
&lt;h3 id="how-to-tune-it">How to tune it&lt;/h3>
&lt;ul>
&lt;li>Dedicated DB server: &lt;strong>60–75% of system RAM&lt;/strong>&lt;/li>
&lt;li>Shared server: be conservative and leave memory for the OS and other services&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'innodb_buffer_pool_size'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="pro-tip">Pro tip&lt;/h3>
&lt;p>If your working set fits in the buffer pool, MySQL feels magical. If it doesn’t, no amount of query tuning will save you.&lt;/p>
&lt;hr>
&lt;h2 id="2-innodb_buffer_pool_instances">2. &lt;code>innodb_buffer_pool_instances&lt;/code>&lt;/h2>
&lt;p>This one matters once memory gets big.&lt;/p>
&lt;h3 id="what-it-does-1">What it does&lt;/h3>
&lt;p>Splits the buffer pool into multiple instances to reduce internal mutex contention.&lt;/p>
&lt;h3 id="how-to-tune-it-1">How to tune it&lt;/h3>
&lt;ul>
&lt;li>Only relevant if buffer pool is &lt;strong>≥ 1GB&lt;/strong>&lt;/li>
&lt;li>Rule of thumb: &lt;strong>1 instance per 1–2GB&lt;/strong>, max 8&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'innodb_buffer_pool_instances'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="gotcha">Gotcha&lt;/h3>
&lt;p>More is not always better. Too many instances wastes memory and can hurt performance.&lt;/p>
&lt;hr>
&lt;h2 id="3-innodb_log_file_size">3. &lt;code>innodb_log_file_size&lt;/code>&lt;/h2>
&lt;h3 id="real-metrics-to-watch-1">Real metrics to watch&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_log%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Pay attention to:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Innodb_log_waits&lt;/code>&lt;/li>
&lt;li>&lt;code>Innodb_log_write_requests&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>If &lt;code>Innodb_log_waits&lt;/code> is non-zero&lt;/strong>, redo logs are too small for your write rate.&lt;/p>
&lt;h3 id="example-graph-1">Example graph&lt;/h3>
&lt;p>Graph &lt;code>Innodb_log_waits&lt;/code> as a rate per second. Ideally, this line hugs zero like it’s afraid of heights.&lt;/p>
&lt;p>This variable controls how calmly MySQL handles write-heavy workloads.&lt;/p>
&lt;h3 id="what-it-does-2">What it does&lt;/h3>
&lt;p>Defines the size of redo logs. Larger logs mean fewer checkpoints and smoother writes.&lt;/p>
&lt;h3 id="how-to-tune-it-2">How to tune it&lt;/h3>
&lt;ul>
&lt;li>OLTP workloads: &lt;strong>1–4GB total redo log&lt;/strong> is common&lt;/li>
&lt;li>Large transactions benefit from larger logs&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'innodb_log_file_size'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="warning">Warning&lt;/h3>
&lt;p>Changing this requires a restart. Plan accordingly or accept the wrath of your on-call future self.&lt;/p>
&lt;hr>
&lt;h2 id="4-innodb_flush_log_at_trx_commit">4. &lt;code>innodb_flush_log_at_trx_commit&lt;/code>&lt;/h2>
&lt;h3 id="real-metrics-to-watch-2">Real metrics to watch&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Innodb_os_log_fsyncs'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Switching from &lt;code>1&lt;/code> to &lt;code>2&lt;/code> often reduces fsyncs by &lt;strong>orders of magnitude&lt;/strong>.&lt;/p>
&lt;h3 id="example-graph-2">Example graph&lt;/h3>
&lt;p>Overlay two lines:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Transactions per second&lt;/code>&lt;/li>
&lt;li>&lt;code>Innodb_os_log_fsyncs per second&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>On busy systems, this graph alone can justify the change to skeptical auditors.&lt;/p>
&lt;p>Performance versus durability, the eternal duel.&lt;/p>
&lt;h3 id="what-it-does-3">What it does&lt;/h3>
&lt;p>Controls how often redo logs are flushed to disk.&lt;/p>
&lt;h3 id="common-values">Common values&lt;/h3>
&lt;ul>
&lt;li>&lt;code>1&lt;/code> – Safest, slowest (flush every commit)&lt;/li>
&lt;li>&lt;code>2&lt;/code> – Very popular compromise&lt;/li>
&lt;li>&lt;code>0&lt;/code> – Fast, risky&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'innodb_flush_log_at_trx_commit'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="reality-check">Reality check&lt;/h3>
&lt;p>For many production systems, &lt;strong>&lt;code>2&lt;/code> delivers massive performance gains&lt;/strong> with acceptable risk, especially with reliable storage.&lt;/p>
&lt;hr>
&lt;h2 id="5-innodb_flush_method">5. &lt;code>innodb_flush_method&lt;/code>&lt;/h2>
&lt;p>This decides how MySQL talks to your disks.&lt;/p>
&lt;h3 id="what-it-does-4">What it does&lt;/h3>
&lt;p>Controls whether MySQL uses OS cache or bypasses it.&lt;/p>
&lt;h3 id="recommended">Recommended&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">ini&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="na">innodb_flush_method&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">O_DIRECT&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This avoids double-buffering between MySQL and the OS page cache.&lt;/p>
&lt;h3 id="caveat">Caveat&lt;/h3>
&lt;p>Some filesystems and older kernels behave differently. Always test.&lt;/p>
&lt;hr>
&lt;h2 id="6-max_connections">6. &lt;code>max_connections&lt;/code>&lt;/h2>
&lt;p>This is not a performance knob. It is a &lt;strong>damage limiter&lt;/strong>.&lt;/p>
&lt;h3 id="what-it-does-5">What it does&lt;/h3>
&lt;p>Caps the number of concurrent client connections.&lt;/p>
&lt;h3 id="why-it-matters">Why it matters&lt;/h3>
&lt;p>Each connection consumes memory. Too many and MySQL dies spectacularly.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'max_connections'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="advice">Advice&lt;/h3>
&lt;ul>
&lt;li>Set it realistically&lt;/li>
&lt;li>Use connection pooling&lt;/li>
&lt;li>Monitor &lt;code>Threads_connected&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="7-thread_cache_size">7. &lt;code>thread_cache_size&lt;/code>&lt;/h2>
&lt;h3 id="real-metrics-to-watch-3">Real metrics to watch&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Threads%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Key fields:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Threads_created&lt;/code>&lt;/li>
&lt;li>&lt;code>Connections&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>If &lt;code>Threads_created / Connections&lt;/code> stays above a few percent, your cache is undersized.&lt;/p>
&lt;h3 id="example-graph-3">Example graph&lt;/h3>
&lt;p>Graph &lt;code>Threads_created&lt;/code> as a counter. A healthy system shows a curve that flattens over time, not a staircase.&lt;/p>
&lt;p>Small change, measurable win.&lt;/p>
&lt;h3 id="what-it-does-6">What it does&lt;/h3>
&lt;p>Caches threads so MySQL doesn’t constantly create and destroy them.&lt;/p>
&lt;h3 id="how-to-tune">How to tune&lt;/h3>
&lt;p>Watch:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Threads_created'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If it keeps climbing, increase &lt;code>thread_cache_size&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="8-table_open_cache-and-table_definition_cache">8. &lt;code>table_open_cache&lt;/code> and &lt;code>table_definition_cache&lt;/code>&lt;/h2>
&lt;p>Metadata matters more than people expect.&lt;/p>
&lt;h3 id="what-they-do">What they do&lt;/h3>
&lt;p>Cache open tables and table definitions to avoid repeated filesystem access.&lt;/p>
&lt;h3 id="symptoms-of-being-too-low">Symptoms of being too low&lt;/h3>
&lt;ul>
&lt;li>High &lt;code>Opened_tables&lt;/code>&lt;/li>
&lt;li>Metadata lock waits&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'table_open_cache'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VARIABLES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'table_definition_cache'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h2 id="9-tmp_table_size-and-max_heap_table_size">9. &lt;code>tmp_table_size&lt;/code> and &lt;code>max_heap_table_size&lt;/code>&lt;/h2>
&lt;h3 id="real-metrics-to-watch-4">Real metrics to watch&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SHOW&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">GLOBAL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">LIKE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Created_tmp%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Watch:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Created_tmp_tables&lt;/code>&lt;/li>
&lt;li>&lt;code>Created_tmp_disk_tables&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>If disk temp tables exceed &lt;strong>5–10%&lt;/strong> of total temp tables, queries are spilling to disk.&lt;/p>
&lt;h3 id="example-graph-4">Example graph&lt;/h3>
&lt;p>Stacked area chart:&lt;/p>
&lt;ul>
&lt;li>In-memory temp tables&lt;/li>
&lt;li>Disk-based temp tables&lt;/li>
&lt;/ul>
&lt;p>Disk usage creeping upward usually points to reporting queries pretending to be OLTP.&lt;/p>
&lt;p>Disk-based temp tables are silent performance killers.&lt;/p>
&lt;h3 id="what-they-do-1">What they do&lt;/h3>
&lt;p>Limit how large in-memory temp tables can grow.&lt;/p>
&lt;h3 id="how-to-tune-1">How to tune&lt;/h3>
&lt;p>Set both to the same value:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">ini&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="na">tmp_table_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">256M&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">max_heap_table_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">256M&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="reality">Reality&lt;/h3>
&lt;p>This helps complex queries, but bad queries still need fixing.&lt;/p>
&lt;hr>
&lt;h2 id="10-slow_query_log-and-long_query_time">10. &lt;code>slow_query_log&lt;/code> and &lt;code>long_query_time&lt;/code>&lt;/h2>
&lt;p>Not a performance variable, but a performance &lt;em>revelation&lt;/em>.&lt;/p>
&lt;h3 id="why-it-matters-1">Why it matters&lt;/h3>
&lt;p>You cannot tune what you cannot see.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">ini&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="na">slow_query_log&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">ON&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">long_query_time&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This turns guesswork into evidence.&lt;/p>
&lt;hr>
&lt;h2 id="a-note-on-graphing-these-metrics">A Note on Graphing These Metrics&lt;/h2>
&lt;p>You don’t need exotic tools. These work well:&lt;/p>
&lt;ul>
&lt;li>&lt;code>performance_schema&lt;/code>&lt;/li>
&lt;li>&lt;code>sys&lt;/code> schema views&lt;/li>
&lt;li>Prometheus + mysqld_exporter&lt;/li>
&lt;li>Percona Monitoring and Management (PMM)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Golden rule:&lt;/strong> Always graph rates, not raw counters.&lt;/p>
&lt;hr>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>Tuning MySQL is less about endless knobs and more about &lt;strong>understanding pressure points&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Memory first&lt;/li>
&lt;li>I/O second&lt;/li>
&lt;li>Concurrency third&lt;/li>
&lt;/ul>
&lt;p>Most performance wins come from &lt;strong>a handful of variables&lt;/strong>, not heroic config files full of folklore.&lt;/p>
&lt;p>If you tune one thing today, make it the buffer pool. If you tune two, add redo logs. Everything else is refinement.&lt;/p>
&lt;p>And if you’re bored again tomorrow, congratulations. You’re officially a database person.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Percona</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>tuning</category><category>innodb</category><media:thumbnail url="https://percona.community/blog/2026/02/tuning_mysql_for_performance_hu_90816541b912e5ef.jpg"/><media:content url="https://percona.community/blog/2026/02/tuning_mysql_for_performance_hu_63ec80e68bf1ede3.jpg" medium="image"/></item><item><title>OIDC in PostgreSQL: With Keycloak</title><link>https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/</link><guid>https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/</guid><pubDate>Mon, 19 Jan 2026 00:00:00 UTC</pubDate><description>We spent a long time, two blog posts to be specific, talking about OAuth/OIDC in theory. Now we’ll take a more practical look at the topic: how can we configure PostgreSQL with a popular open source identity provider, Keycloak, and our pg_oidc_validator plugin?</description><content:encoded>&lt;p>We spent a long time, &lt;a href="https://percona.community/blog/2025/11/07/oauth-oidc-validators/">two&lt;/a> blog &lt;a href="https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/">posts&lt;/a> to be specific, talking about OAuth/OIDC in theory.
Now we’ll take a more practical look at the topic:
how can we configure PostgreSQL with a popular open source identity provider, &lt;a href="https://www.keycloak.org/" target="_blank" rel="noopener noreferrer">Keycloak&lt;/a>, and our &lt;a href="https://github.com/percona/pg_oidc_validator" target="_blank" rel="noopener noreferrer">pg_oidc_validator&lt;/a> plugin?&lt;/p>
&lt;p>We’ll not only look at the PostgreSQL configuration part, but also discuss the environment requirements and setting up Keycloak.&lt;/p>
&lt;h3 id="docker-containers">Docker containers&lt;/h3>
&lt;p>If you are only interested in trying out a working demo installation, we have a ready-to-use Docker Compose configuration available &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/tree/main/examples/keycloak" target="_blank" rel="noopener noreferrer">in our GitHub repo&lt;/a>.
This setup includes a Keycloak instance, a PostgreSQL server, and a utility container that runs &lt;code>psql&lt;/code>, all running in different containers, simulating different machines.&lt;/p>
&lt;pre class="mermaid">
graph TB
subgraph Host["Host Machine"]
User["👤 User"]
Browser["🌐 Browser"]
end
subgraph DockerNetwork["Docker Network"]
Keycloak["🔐 Keycloak Container"]
PG["🗄️ PostgreSQL Container"]
PSQLClient["💻 psql Container"]
end
User --- Browser
User --- PSQLClient
Browser --- Keycloak
PSQLClient --- Keycloak
PSQLClient --- PG
PG --- Keycloak
style User fill:#e1f5ff
style Browser fill:#fff4e6
style Keycloak fill:#ffe6e6
style PG fill:#e6ffe6
style PSQLClient fill:#f0e6ff
style Host fill:#f5f5f5
style DockerNetwork fill:#e8f4f8
&lt;/pre>
&lt;p>&lt;strong>Warning:&lt;/strong>
This is a demo environment, intended only for testing purposes.
Do not use it in production.&lt;/p>
&lt;p>Alternatively, if you are only interested in configuring PostgreSQL, you can use this setup to start up Keycloak, and only focus on the &lt;a href="#configuring-postgresql">PostgreSQL related sections of this post&lt;/a>.&lt;/p>
&lt;p>Also note:
while it is a ready-to-use configuration, with everything set up… it’s missing one bit:
because it tries to do everything correctly, including running every service in a different container, we have to use hostnames; we can’t just use &lt;code>localhost&lt;/code> everywhere.
This means that the Keycloak service uses the &lt;code>keycloak&lt;/code> hostname as its name, and the host OS needs to be able to resolve this to use the device authorization flow.
In most operating systems, this requires editing the hosts file - detailed instructions are shown later.&lt;/p>
&lt;h3 id="running-keycloak-with-https">Running Keycloak (with HTTPS)&lt;/h3>
&lt;p>Keycloak itself has a ready-to-use Docker image for trying it out.
Executing it is quite simple, but this default setup results in an unsecure setup, which is not enough for us:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker run -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The above command starts up a container with a freshly initialized provider, with an admin user and admin password, and exposes it on port 8080 (on every interface).
This is a nice way to try out the UI and start discovering Keycloak, but it has some limitations:&lt;/p>
&lt;p>Authentication/authorization has to be secure, and that means it has to use secure transport layers.
While the OAuth standard doesn’t specify an explicit protocol, &lt;a href="https://www.rfc-editor.org/rfc/rfc9700" target="_blank" rel="noopener noreferrer">RFC 9700&lt;/a>, which defines best practices, clearly showcases HTTPS everywhere.&lt;/p>
&lt;p>But to use that, we first need certificates for the encrypted connection.&lt;/p>
&lt;h3 id="how-do-i-get-certificates">How do I get certificates?&lt;/h3>
&lt;p>Depending on the exact demo environment, we have two choices:&lt;/p>
&lt;ul>
&lt;li>If the demo environment uses a public domain, the proper approach is to use a certificate signed by a trusted third party.
There are free authorities like &lt;a href="https://letsencrypt.org/" target="_blank" rel="noopener noreferrer">Let’s Encrypt&lt;/a> or &lt;a href="https://zerossl.com/" target="_blank" rel="noopener noreferrer">ZeroSSL&lt;/a> for quick setups.&lt;/li>
&lt;li>In the more likely case where the demo environment is private, we have to use self-signed certificates.
The rest of the blog post will discuss this approach.&lt;/li>
&lt;/ul>
&lt;p>Generating a simple self-signed certificate is easy with OpenSSL.
The following is a sample command that can run non-interactively, without asking additional questions - but again it has some environment dependency, the hostname, which we first have to figure out:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out crt.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=&lt;hostname>"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It generates a certificate that is valid for 10 years, for &lt;code>hostname&lt;/code>.
The &lt;code>hostname&lt;/code> part is important:
to enforce security, PostgreSQL validates the TLS certificate’s hostname against the issuer URL.
If the hostname in the OAuth issuer URL and the certificate’s Common Name (or Subject Alternative Name) don’t match, it won’t proceed with the login.&lt;/p>
&lt;p>If you plan to run Keycloak in a Docker container but run PostgreSQL directly on the host machine, Docker exposes the 8443 port used by Keycloak on localhost, both the browser used to complete the login process and PostgreSQL can refer to the issuer as &lt;code>https://localhost:8443/...&lt;/code>, which means the hostname can be &lt;code>localhost&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out crt.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=localhost"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But if you intend to follow the Docker compose setup, where Keycloak and PostgreSQL are two separate containers, this no longer works:
the port mapping only exposes the Keycloak service for the host, not for the PostgreSQL container.
That container has to refer to it as “https://keycloak:8443/…”&lt;/p>
&lt;p>Which means that in this scenario, we have to use &lt;code>CN=keycloak&lt;/code> instead.
Or alternatively, you can generate a certificate that includes both hostnames, this is what the docker compose example configuration does:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out crt.pem -sha256 -days 3650 -nodes \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=keycloak" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -addext "subjectAltName=DNS:keycloak,DNS:localhost,IP:127.0.0.1"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="back-to-keycloak">Back to Keycloak&lt;/h3>
&lt;p>To run Keycloak with HTTPS, we have to use a different port, and specify the certificates&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker run -p 127.0.0.1:8443:8443 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin -e KC_HTTPS_CERTIFICATE_FILE=/keys/crt.pem -e KC_HTTPS_CERTIFICATE_KEY_FILE=/keys/key.pem -v /path/to/the/keys:/keys/ quay.io/keycloak/keycloak:latest start-dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command specifies two more environment variables, the filenames of the certificate and the private key, and mounts the directory containing them.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong>
Please note that we used &lt;code>127.0.0.1:8443:8443&lt;/code> instead of simply &lt;code>8443:8443&lt;/code>
It is a good practice to not expose admin interfaces with default passwords publicly.&lt;/p>
&lt;h4 id="trusting-the-certificates">Trusting the certificates&lt;/h4>
&lt;p>Now that we have a running Keycloak instance with HTTPS certificates, we need to make sure our systems trust them.&lt;/p>
&lt;p>When you open a browser and navigate to a website with a self-signed certificate, you’ll get a warning.
After acknowledging the warning you can proceed and use the website normally.&lt;/p>
&lt;p>Similarly, software using HTTPS for communications usually defaults to proper certificate verification, but often also allows administrators to either disable the certificate check – not safe in production, but useful for quick demos like this – or to manually specify a certificate authority used for verification.&lt;/p>
&lt;p>Unfortunately at this point this isn’t the case for PostgreSQL, it doesn’t provide such options.
The operating system has to trust the certificates on both the server and client host, otherwise it will refuse to complete the OIDC authentication flow.&lt;/p>
&lt;p>If you used the approach with a public domain and generally trusted authority, this is not an issue.
But if you generated a self-signed certificate instead, you won’t be able to authenticate unless you make the systems running PostgreSQL trust this certificate.&lt;/p>
&lt;p>On most Linux systems, this is as simple as copying the certificate (&lt;code>crt.pem&lt;/code>) to a specific directory, and running a system script that updates the trusted certificates.
For example, on Ubuntu:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo cp crt.pem /usr/local/share/ca-certificates/keycloak-test.crt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo update-ca-certificates&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Also, we shouldn’t forget that we can have up to 4 different systems:&lt;/p>
&lt;ul>
&lt;li>the Keycloak server&lt;/li>
&lt;li>the PostgreSQL Server&lt;/li>
&lt;li>the system running the psql client&lt;/li>
&lt;li>and another system running the browser which completes the device flow&lt;/li>
&lt;/ul>
&lt;p>This is the case with our Docker Compose example - 3 of these are containers, and the browser runs on the host machine.
Browsers usually ignore certificates placed in the above folder.
But that’s not an issue, you can acknowledge the warning and still use the website.
The only part where trust matters is the &lt;code>libcurl&lt;/code> library used by PostgreSQL, and that uses the certificates trusted by the system.&lt;/p>
&lt;p>The host system only has to trust the certificate if it is also used to run PostgreSQL.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong>
Please do not add random certificates to your everyday OS, or at least remember to delete them later.&lt;/p>
&lt;h4 id="recognizing-the-keycloak-host">Recognizing the Keycloak host&lt;/h4>
&lt;p>Besides trusting the certificates, there’s another hostname-related configuration we need to address.&lt;/p>
&lt;p>Even if you run PostgreSQL directly on the host machine, it is still possible to access Keycloak using the ‘https://keycloak’ URL instead of localhost – and if you do run the PostgreSQL server in a container, you have to use this form.&lt;/p>
&lt;p>For this to work, all 3 systems that need to connect to Keycloak (the PostgreSQL server, the psql client, and the browser) have to recognize this hostname.
When using &lt;code>docker compose&lt;/code>, this hostname resolution will work directly in the other containers, but not on the host itself.&lt;/p>
&lt;p>To make this work on the host, or on any other machine that requires it, you have to edit the hosts file:&lt;/p>
&lt;p>&lt;strong>On Linux/Mac:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">"127.0.0.1 keycloak"&lt;/span> &lt;span class="p">|&lt;/span> sudo tee -a /etc/hosts&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>On Windows:&lt;/strong> Edit &lt;code>C:\Windows\System32\drivers\etc\hosts&lt;/code> as Administrator and add:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">127.0.0.1 keycloak&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="cant-i-just-use-localhost-in-the-browser-instead">Can’t I just use localhost in the browser instead?&lt;/h4>
&lt;p>You might be wondering if there’s a shortcut here.&lt;/p>
&lt;p>Even if the PostgreSQL container has to reference Keycloak as &lt;code>keycloak&lt;/code>, your host still sees the exposed port as &lt;code>localhost:8443&lt;/code>.
So can you just use this in the browser instead, and complete the authentication that way, without editing the hosts file?&lt;/p>
&lt;p>The answer is unfortunately no.
There are two possible scenarios:&lt;/p>
&lt;ul>
&lt;li>If Keycloak is configured with strict hostname, it will try to redirect the browser to “https://keycloak…” during the authorization process&lt;/li>
&lt;li>If Keycloak is configured with dynamic hostname, it will complete the process with “https://localhost”, but it will also use “localhost” in the generated access tokens instead of “keycloak”.
When our validator checks the token, it will notice this discrepancy and reject the login attempt.&lt;/li>
&lt;/ul>
&lt;h3 id="configuring-your-realm">Configuring your realm&lt;/h3>
&lt;p>With the Keycloak infrastructure setup out of the way, we can now focus on configuring Keycloak itself.&lt;/p>
&lt;p>After you have your Keycloak instance up and running, it is time to open a browser and navigate to &lt;code>https://keycloak:8443&lt;/code>.
A quick login with “admin” and “admin”, and the browser already displays the admin UI with the default master realm.&lt;/p>
&lt;p>A complete detailed introduction is out of scope for this blog post – the &lt;a href="https://www.keycloak.org/documentation" target="_blank" rel="noopener noreferrer">Keycloak documentation&lt;/a> is much better for that – we will only try to explain the minimum required to set up a relatively simple, but secure configuration for our PostgreSQL instance.
The steps we show here will be similar to what our demo setup also uses.&lt;/p>
&lt;p>Let’s start by creating a new realm, under the “Manage realms” menu:
realms are the main building blocks of isolation in Keycloak, storing users, clients, and everything, so it’s a good practice not to use the default one.
In our example, we named our realm &lt;code>pgrealm&lt;/code>.
This name is important, as it will be included in the OAuth issuer URL.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1603" height="905" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/01/keycloak_step1_realm_hu_bc8abb77b426b9be.webp 768w, https://percona.community/blog/2026/01/keycloak_step1_realm_hu_92639b48d16a4100.webp 480w, https://percona.community/blog/2026/01/keycloak_step1_realm_hu_c9a9e6dd2adc1729.webp 1400w" src="https://percona.community/blog/2026/01/keycloak_step1_realm_hu_c9a9e6dd2adc1729.webp" alt="Creating a new realm in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After hitting “Create”, the new realm is automatically set as current, and we can continue configuring it.&lt;/p>
&lt;p>Let’s continue by creating our “testuser” under “Users”.
Select that the email is verified, fill the requested email, first name and last name fields, and hit create.
If you miss some of these fields, Keycloak will ask you to complete them during the first login.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1109" height="845" sizes="(max-width: 1109px) 100vw, 1109px" srcset="https://percona.community/blog/2026/01/keycloak_step2_user_hu_b5cce387707c91a8.webp 768w, https://percona.community/blog/2026/01/keycloak_step2_user_hu_81e165e3903402c7.webp 480w, https://percona.community/blog/2026/01/keycloak_step2_user_hu_eda78e640da9a043.webp 1109w" src="https://percona.community/blog/2026/01/keycloak_step2_user_hu_eda78e640da9a043.webp" alt="Creating a user in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After the user is created, navigate to the “Credentials” tab on the displayed user admin page, and set a password.
Also remove the checkbox from “Temporary”, unless you want to change it during the first login.
In our example setup, we used “asdfasdf”.
This is of course only appropriate for a quick demo setup, use a better one for anything else.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1005" height="580" sizes="(max-width: 1005px) 100vw, 1005px" srcset="https://percona.community/blog/2026/01/keycloak_step3_user_hu_9299d769ae3f3b50.webp 768w, https://percona.community/blog/2026/01/keycloak_step3_user_hu_5afe8ad126cf03b7.webp 480w, https://percona.community/blog/2026/01/keycloak_step3_user_hu_398f737b88f86129.webp 1005w" src="https://percona.community/blog/2026/01/keycloak_step3_user_hu_398f737b88f86129.webp" alt="Setting user credentials in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After creating our user, let’s create a client under “Clients” with the “Create Client” button.
The first screen asks for a client ID – this will be required for the &lt;code>psql&lt;/code> command – and a name and description – these will be displayed on the authorization page by the browser.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1842" height="744" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/01/keycloak_step4_client_hu_bfe7d9efc3a444b1.webp 768w, https://percona.community/blog/2026/01/keycloak_step4_client_hu_f9a554df5f58df39.webp 480w, https://percona.community/blog/2026/01/keycloak_step4_client_hu_af6c6feecff39bec.webp 1400w" src="https://percona.community/blog/2026/01/keycloak_step4_client_hu_af6c6feecff39bec.webp" alt="Creating a client in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After clicking next, the next screen configures how OIDC should work exactly.&lt;/p>
&lt;p>The first toggle, “Client authentication” can be both on and off – this controls if the client requires a secret, or only an ID.
As we discussed earlier, &lt;code>psql&lt;/code> is a public client, and while it can use a secret, it’s not really a secret.
Adding a secret only adds complexity while not providing more security, as we also have to specify that during the connection call, but it is supported.&lt;/p>
&lt;p>For the “authentication flow” we have to check “OAuth 2.0 Device Authorization Grant” to enable the device flow, and we can leave everything else on default.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1373" height="817" sizes="(max-width: 1373px) 100vw, 1373px" srcset="https://percona.community/blog/2026/01/keycloak_step5_client_hu_bfd1ad656fbcd490.webp 768w, https://percona.community/blog/2026/01/keycloak_step5_client_hu_bead53410d5c3c96.webp 480w, https://percona.community/blog/2026/01/keycloak_step5_client_hu_e97a53955453b011.webp 1373w" src="https://percona.community/blog/2026/01/keycloak_step5_client_hu_e97a53955453b011.webp" alt="Configuring client authentication flow in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The final third screen doesn’t require any changes – those are settings for HTTP-based flows, but we are using the device flow.
If you are configuring PostgreSQL OIDC with a web application, of course you should fill these properly.&lt;/p>
&lt;p>After creating our client, it’s also a good practice to make our user consent more explicit:
as we tried to make this clear in earlier blog posts, this is the only line of defense with OAuth-based logins:
administrators have to be very clear about displaying where the user logs in.&lt;/p>
&lt;p>First, if you scroll down on the Client administration page displayed after creation, there’s a section about the consent screen.
It’s a good practice to make this as explicit as possible, with a nice custom message for the users.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1236" height="578" sizes="(max-width: 1236px) 100vw, 1236px" srcset="https://percona.community/blog/2026/01/keycloak_step6_client_hu_4110d1bf451127ea.webp 768w, https://percona.community/blog/2026/01/keycloak_step6_client_hu_47c317fbbfa0f57e.webp 480w, https://percona.community/blog/2026/01/keycloak_step6_client_hu_1e8d4188a95371f.webp 1236w" src="https://percona.community/blog/2026/01/keycloak_step6_client_hu_1e8d4188a95371f.webp" alt="Configuring consent screen in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After saving this, we also should add a client scope using the menu item below “Clients”.
The name attribute is what we’ll have to specify in our PostgreSQL configuration.
The two toggles below are both required: “include in token scope” means that it will be included in the JWT; without that, the validator won’t be able to verify the presence of the scope.
“Display on consent screen” means that this is a scope that should be explicitly displayed to the user during login.&lt;/p>
&lt;p>(We’ll discuss what scopes are and why they matter in the next section.)&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1870" height="834" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/01/keycloak_step7_scope_hu_85db394d2a8ecd6e.webp 768w, https://percona.community/blog/2026/01/keycloak_step7_scope_hu_f8569a509e3c1027.webp 480w, https://percona.community/blog/2026/01/keycloak_step7_scope_hu_94bd02d074f6502a.webp 1400w" src="https://percona.community/blog/2026/01/keycloak_step7_scope_hu_94bd02d074f6502a.webp" alt="Creating a client scope in Keycloak" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And with this, our basic Keycloak configuration is ready!&lt;/p>
&lt;h3 id="what-is-a-scope">What is a scope?&lt;/h3>
&lt;p>The scope configuration in the previous section might seem confusing:
why did we configure consent screen settings at multiple locations?
What is this scope concept actually about?&lt;/p>
&lt;p>To answer this question, we have to remember our earlier examples, where we discussed that PostgreSQL itself is not a client (application), but it is just something used by possibly multiple clients.
In the real world, that client could be “psql”, or “EditorApp”, or anything else that uses PostgreSQL while not providing more security.&lt;/p>
&lt;p>PostgreSQL could use a list of allowed clients for authorization – but it doesn’t do that.
Instead it relies on another OAuth concept, scopes.&lt;/p>
&lt;p>The OAuth scope is a mechanism intended to limit what a token is allowed to access.
There are some generic scopes, such as “email” or “profile”.
And there are many application specific scopes:
cloud providers like Google or Azure use them to control which services a token is allowed to access.
For example, if you grant something the ability to add entries to your calendar, it won’t be allowed to read your emails or location history.&lt;/p>
&lt;p>The same way, you can think of scopes with PostgreSQL as “which database the user can access”.
Database here could mean both a PostgreSQL instance, or just a single database in it – as OAuth is configured in pg_hba, it is possible to configure different required scopes for different databases (more about this later).&lt;/p>
&lt;p>During the Keycloak configuration we recommended configuring an explicit consent screen for the client, but technically that part is outside of the database configuration.
A database can be accessed by multiple clients, some of those might be created way later in time.&lt;/p>
&lt;p>Even if somebody doesn’t configure that part, a scope with a required consent screen will be displayed, if the client requests it.
And that is part of the database configuration – as we can configure PostgreSQL explicitly to require specific scopes in its configuration.&lt;/p>
&lt;h3 id="configuring-postgresql">Configuring PostgreSQL&lt;/h3>
&lt;p>Now that we have a working, properly configured Keycloak server, it’s time to configure the PostgreSQL side of the equation.
While we do have a working Docker example as part of the Docker Compose configuration, this is currently tricky to set up:
there is no Docker image with our pg_oidc_validator.&lt;/p>
&lt;p>You can check the &lt;a href="link">relevant section of the compose configuration&lt;/a>, but it requires a custom entry point and a short bash script.&lt;/p>
&lt;p>In this blog post we’ll focus on the actual manual steps instead.&lt;/p>
&lt;h4 id="required-packages">Required packages&lt;/h4>
&lt;p>On the server, we need the PostgreSQL 18 server packages installed, and also the pg_oidc_validator package.
For the validator, we currently have downloadable deb and rpm packages on our &lt;a href="https://github.com/percona/pg_oidc_validator/releases" target="_blank" rel="noopener noreferrer">GitHub releases page&lt;/a>, and packages for SUSE can be found in the &lt;a href="https://software.opensuse.org/package/pg_oidc_validator" target="_blank" rel="noopener noreferrer">official SUSE packages&lt;/a>.&lt;/p>
&lt;p>On the client side, we need the PostgreSQL 18 client along with the OAuth client package – this is usually a separate package named libpq-oauth on most distributions and requires separate installation&lt;/p>
&lt;h4 id="setting-up-a-data-directory">Setting up a data directory&lt;/h4>
&lt;p>Let’s just assume that we have a data directory initialized somewhere:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">initdb -D datadir&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We will have to modify a few configuration files for OIDC to work.&lt;/p>
&lt;p>Let’s start by enabling the validator in &lt;code>datadir/postgresql.conf&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">oauth_validator_libraries = pg_oidc_validator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pg_oidc_validator.authn_field = email&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The first line tells PostgreSQL to load the Percona pg_oidc_validator, and the second line is a specific configuration parameter for our validator – it tells it that we want to map Keycloak users to PostgreSQL users based on the email claim in the access token.
This second line is optional; it defaults to the “sub” (subject) field, which identifies the user in most OIDC providers.
However, Keycloak doesn’t allow us to customize the value of this field, and it returns a non-user-friendly identifier in it.
In practice, it is clearer to use a more verbose field for mapping, such as email, so we’ll use that in our example.&lt;/p>
&lt;p>After modifying this file, we can try to start the server.
If all required packages are installed correctly, and there isn’t another PostgreSQL instance running on the default port, it should start without issues:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pg_ctl -D datadir start&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With the server running, let’s create a matching PostgreSQL user for our testuser:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">createuser testuser&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Next, we have to tell PostgreSQL that we want to use the Keycloak instance we set up previously.
Let’s add an entry to &lt;code>datadir/pg_hba.conf&lt;/code> to reference the OIDC provider.
Add this line to the beginning of the file, before the &lt;code>trust&lt;/code> sections – since pg_hba is executed line by line, if the &lt;code>trust&lt;/code> entries are before the OAuth entry, authentication will never reach that line:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">host all all 0.0.0.0/0 oauth scope="email pgscope",issuer=https://keycloak:8443/realms/pgrealm,map=kcmap&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Warning:&lt;/strong>
In a real-world setup, you would have to remove all trust entries.
The &lt;code>trust&lt;/code> authentication method allows connections without any password verification and should never be used in production.&lt;/p>
&lt;p>This entry adds the OAuth option for connections using IPv4.
We specify that we require “pgscope”, which is the example scope with the custom consent screen we created earlier in the Keycloak configuration, and the “email” scope, which instructs Keycloak to include the user’s email address in the JWT (this will also be presented on the consent screen).&lt;/p>
&lt;p>The latter is required because in this example we map our database users to the users on the identity provider using their email address – this will be configured in more detail in the “kcmap” referenced in the configuration line.&lt;/p>
&lt;p>The only required parameter to OAuth is the “issuer”; everything else is optional.
But providing required scopes is a good practice, and it is required for proper security, as we explained earlier.
There are also other parameters not mentioned here, a full list is available in the &lt;a href="https://www.postgresql.org/docs/current/auth-oauth.html" target="_blank" rel="noopener noreferrer">PostgreSQL documentation&lt;/a>.&lt;/p>
&lt;p>One potentially important parameter is the “validator”.
PostgreSQL allows multiple different pg_hba OAuth entries, and it also allows multiple different validators:
the configuration parameter in &lt;code>postgresql.conf&lt;/code> is called &lt;code>oauth_validator_libraries&lt;/code>.
If that parameter actually contains multiple libraries, the “validator” parameter becomes required for OAuth entries in pg_hba conf, and has to match an entry in the validator list.
Otherwise, it is assumed that all OAuth entries use the one available validator.&lt;/p>
&lt;p>The above means that the map setting is also optional.
There are scenarios where it’s not needed, such as if the identity provider has a claim that directly contains user names matching PostgreSQL usernames, we could use that field instead and skip the manual mapping.&lt;/p>
&lt;p>Following our example, let’s define an entry in &lt;code>datadir/pg_ident.conf&lt;/code> for the &lt;code>kcmap&lt;/code>.
We only need a single entry for our testuser:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># MAPNAME SYSTEM-USERNAME DATABASE-USERNAME
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kcmap testuser@example.com testuser&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>All that’s left is to reload our configuration:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pg_ctl -D datadir reload&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="connecting-to-the-database">Connecting to the database&lt;/h4>
&lt;p>With all the configuration in place, it’s time for the moment of truth:
actually connecting to PostgreSQL using OIDC!&lt;/p>
&lt;p>With the server properly configured, we are ready to connect to it using psql.
To do so, we have to use a command similar to the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bin/psql -h 127.0.0.1 'dbname=postgres oauth_issuer=https://keycloak:8443/realms/pgrealm oauth_client_id=pgclient'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note that we have to repeat the issuer URL here.
It is always required, even if the PostgreSQL configuration only contains one OAuth issuer.
This URL has to match &lt;strong>exactly&lt;/strong> the issuer URL in a pg_hba line, and both should &lt;strong>exactly&lt;/strong> match the issuer URL in the JWT.
If there’s a mismatch anywhere, authentication will fail.
This is why we had to take extra steps previously to make the Keycloak hostname available everywhere.&lt;/p>
&lt;p>This is also the only place where we have to mention a client id – and if we configured an authenticated client in Keycloak, we also have to specify &lt;code>oauth_client_secret&lt;/code>.
The server can work with multiple clients, but now that we are actually starting up a PostgreSQL client, we can specify which OAuth client will we use to complete the authentication flow.&lt;/p>
&lt;p>After we execute this command, psql will display the device authorization instructions, showing us a URL and a device code.&lt;/p>
&lt;p>All we have to do is follow the instructions:&lt;/p>
&lt;ol>
&lt;li>navigate to the specified URL&lt;/li>
&lt;li>enter the device code&lt;/li>
&lt;li>login with our testuser&lt;/li>
&lt;li>confirm the consent screen&lt;/li>
&lt;/ol>
&lt;p>If you are doing all of this in a single session, don’t forget to log out of the admin user session before executing these steps, or use a different browser for it.
We want to log in with the testuser, not with the admin – the admin user doesn’t have a mapping in our PostgreSQL configuration.&lt;/p>
&lt;!-- TODO: Add consent screen screenshot when available -->
&lt;p>While we go through these steps, the &lt;code>psql&lt;/code> client periodically polls Keycloak to see if the flow was completed on the OIDC provider side.
Since this is a periodic polling, done every few seconds, we might have to wait a few seconds before we are logged in to an SQL session.&lt;/p>
&lt;h3 id="thats-all">That’s all!&lt;/h3>
&lt;p>And just like that, we’ve successfully authenticated to PostgreSQL using OIDC!&lt;/p>
&lt;p>With the &lt;code>psql&lt;/code> command logged in, we’ve completed the full circle:
from setting up Keycloak with proper certificates, through configuring realms and clients, to establishing a secure OIDC-authenticated PostgreSQL connection.&lt;/p>
&lt;p>Hopefully these instructions were clear and everything worked on the first try.
If you encountered issues along the way, don’t be discouraged:
OAuth/OIDC is complex, there are many things that could go wrong.
Since this is an important security feature, it has to fail if anything is even slightly wrong.&lt;/p>
&lt;p>In our next post, we’ll focus on errors and failures:
both to help diagnose possible errors with the OAuth flow in PostgreSQL, but also for reassurance:
in an authentication setup not letting unauthorized people log in is just as important as successfully logging in somebody with the proper permissions.
Stay tuned for our examples showcasing how pg_oidc_validator and PostgreSQL’s OAuth support can keep your server safe!&lt;/p>
&lt;p>If you find any issues with our validator, or have comments / feature requests, please reach out to us in our &lt;a href="https://github.com/percona-lab/pg_oidc_validator" target="_blank" rel="noopener noreferrer">Github page&lt;/a>!&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>OIDC</category><category>Security</category><category>Keycloak</category><media:thumbnail url="https://percona.community/blog/2025/11/oidc2_hu_676fa04555556171.jpg"/><media:content url="https://percona.community/blog/2025/11/oidc2_hu_965aca32f15aec8b.jpg" medium="image"/></item><item><title>Configuring the Component Keyring in Percona Server and PXC 8.4</title><link>https://percona.community/blog/2026/01/13/configuring-the-component-keyring-in-percona-server-and-pxc-8.4/</link><guid>https://percona.community/blog/2026/01/13/configuring-the-component-keyring-in-percona-server-and-pxc-8.4/</guid><pubDate>Tue, 13 Jan 2026 00:00:00 UTC</pubDate><description>Configuring the Component Keyring in Percona Server and PXC 8.4 (Or: how to make MySQL encryption boring, which is the goal)</description><content:encoded>&lt;h1 id="configuring-the-component-keyring-in-percona-server-and-pxc-84">Configuring the Component Keyring in Percona Server and PXC 8.4&lt;/h1>
&lt;p>&lt;em>(Or: how to make MySQL encryption boring, which is the goal)&lt;/em>&lt;/p>
&lt;p>Encryption is one of those things everyone agrees is important, right up until MySQL refuses to start and you’re staring at a JSON file wondering which brace ruined your evening.&lt;/p>
&lt;p>With &lt;strong>MySQL 8.4&lt;/strong>, encryption has firmly moved into the &lt;strong>component world&lt;/strong>, and if you’re running &lt;strong>Percona Server 8.4&lt;/strong> or &lt;strong>Percona XtraDB Cluster (PXC) 8.4&lt;/strong>, the supported path forward is the &lt;code>component_keyring_file&lt;/code> component.&lt;/p>
&lt;p>The good news: the setup is mostly identical for Percona Server and PXC.&lt;br>
The bad news: PXC 8.4.4 and 8.4.5 shipped with a bug that makes this less fun than it should be.&lt;/p>
&lt;p>Let’s walk through a setup that works, keeps your keys locked down, and avoids the usual landmines.&lt;/p>
&lt;hr>
&lt;h2 id="step-1-tell-mysql-which-component-to-load">Step 1: Tell MySQL Which Component to Load&lt;/h2>
&lt;p>Components are registered using &lt;strong>JSON&lt;/strong>, not traditional MySQL configuration syntax. This is important, because MySQL will not politely warn you if you get it wrong. It will simply refuse to start.&lt;/p>
&lt;p>Create the file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo vi /usr/sbin/mysqld.my&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"components"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"file://component_keyring_file"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Take a second to double-check the formatting. One missing quote here will cost you more time than you want to admit.&lt;/p>
&lt;p>Now lock it down:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo chown root:root /usr/sbin/mysqld.my
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">644&lt;/span> /usr/sbin/mysqld.my&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is configuration, not data. MySQL only needs to read it.&lt;/p>
&lt;hr>
&lt;h2 id="step-2-prepare-the-keyring-directory-handle-with-care">Step 2: Prepare the Keyring Directory (Handle With Care)&lt;/h2>
&lt;p>This directory will hold encryption keys. Treat it accordingly.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /var/lib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir mysql-keyring
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown mysql:mysql mysql-keyring
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">750&lt;/span> mysql-keyring&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A simple rule that saves headaches:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>mysql owns the keys&lt;/strong>&lt;/li>
&lt;li>&lt;strong>MySQL is allowed to access them&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Nobody else gets any ideas&lt;/strong>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="step-3-configure-the-keyring-component-itself">Step 3: Configure the Keyring Component Itself&lt;/h2>
&lt;p>Next, move to the plugin directory:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /usr/lib64/mysql/plugin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create the component configuration file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo vi component_keyring_file.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"path"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"/var/lib/mysql-keyring/component_keyring_file"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"read_only"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This file tells MySQL where the keyring lives and ensures it can’t be casually modified at runtime.&lt;/p>
&lt;p>Set ownership and permissions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo chown root:root component_keyring_file.cnf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">640&lt;/span> component_keyring_file.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Again: configuration belongs to root. MySQL just reads it.&lt;/p>
&lt;hr>
&lt;h2 id="step-4-the-pxc-844--845-bug-yes-theres-one">Step 4: The PXC 8.4.4 / 8.4.5 Bug (Yes, There’s One)&lt;/h2>
&lt;p>If you’re running &lt;strong>Percona Server&lt;/strong>, you can skip this entire section and enjoy your day.&lt;/p>
&lt;p>If you’re running &lt;strong>Percona XtraDB Cluster 8.4.4 or 8.4.5&lt;/strong>, there is a known issue with plugin paths that prevents the component keyring from loading correctly. This was fixed in &lt;strong>PXC 8.4.6&lt;/strong>.&lt;/p>
&lt;p>If upgrading isn’t an option yet, you’ll need one of the following workarounds.&lt;/p>
&lt;h3 id="option-a-create-a-symlink-preferred">Option A: Create a Symlink (Preferred)&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo ln -s &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>/usr/bin/pxc_extra/pxb-8.4/lib/lib64/xtrabackup/plugin &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>/usr/bin/pxc_extra/pxb-8.4/lib/plugin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="option-b-copy-the-plugin-directory">Option B: Copy the Plugin Directory&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo cp -ar &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>/usr/bin/pxc_extra/pxb-8.4/lib/lib64/xtrabackup/plugin &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>/usr/bin/pxc_extra/pxb-8.4/lib&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you’re on &lt;strong>PXC 8.4.6 or newer&lt;/strong>, this problem is already behind you and you can safely pretend it never existed.&lt;/p>
&lt;hr>
&lt;h2 id="step-5-restart-mysql">Step 5: Restart MySQL&lt;/h2>
&lt;p>Time for the moment of truth:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl restart mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or &lt;code>mysqld&lt;/code>, depending on your system.&lt;/p>
&lt;p>If MySQL starts cleanly, you’re doing well. If not, go back and check your JSON files. It’s almost always the JSON.&lt;/p>
&lt;hr>
&lt;h2 id="step-6-verify-the-keyring-is-actually-loaded">Step 6: Verify the Keyring Is Actually Loaded&lt;/h2>
&lt;p>Never assume. Always verify.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">keyring_component_status&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You should see the &lt;code>component_keyring_file&lt;/code> listed and active. If it’s there, the keyring is live.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="o">+&lt;/span>&lt;span class="c1">---------------------+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS_KEY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STATUS_VALUE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">---------------------+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Component_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">component_keyring_file&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Author&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Oracle&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Corporation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">License&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">GPL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Implementation_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">component_keyring_file&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">Version&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Component_status&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Active&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Data_file&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">keyring&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">component_keyring_file&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Read_only&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Yes&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">---------------------+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h2 id="a-note-for-percona-server-users">A Note for Percona Server Users&lt;/h2>
&lt;p>Percona Server may still include &lt;strong>legacy keyring plugins&lt;/strong> such as:&lt;/p>
&lt;ul>
&lt;li>&lt;code>keyring_file&lt;/code>&lt;/li>
&lt;li>&lt;code>keyring_vault&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Do not mix legacy keyring plugins with component keyrings. They come from different eras of MySQL design and do not coexist peacefully.&lt;/p>
&lt;p>Choose one model. For MySQL 8.4 and forward, &lt;strong>components are the future&lt;/strong>.&lt;/p>
&lt;h2 id="additional-steps-for-percona-xtradb-cluster-pxc">Additional Steps for Percona XtraDB Cluster (PXC)&lt;/h2>
&lt;p>Percona XtraDB Cluster introduces one critical difference compared to standalone Percona Server: the keyring file itself is not replicated by Galera. Only metadata and transactional state are replicated. The encryption keys remain node-local filesystem artifacts and must be handled deliberately.&lt;/p>
&lt;h3 id="node-1-establish-the-authoritative-keyring">Node 1: Establish the Authoritative Keyring&lt;/h3>
&lt;p>Choose a single node to initialize the keyring. This is typically Node1, but the choice itself is not important as long as you are consistent.&lt;/p>
&lt;p>On this node:&lt;/p>
&lt;ul>
&lt;li>Complete all previous steps in this document&lt;/li>
&lt;li>Start MySQL successfully&lt;/li>
&lt;li>Verify the keyring component is loaded:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">keyring_component_status&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once this node is running, the file below will be created and populated:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">swift&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-swift" data-lang="swift">&lt;span class="line">&lt;span class="cl">&lt;span class="o">/&lt;/span>&lt;span class="kd">var&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">keyring&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">component_keyring_file&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This file becomes the authoritative source of encryption keys for the entire cluster.&lt;/p>
&lt;h3 id="why-the-keyring-file-must-be-copied">Why the Keyring File Must Be Copied&lt;/h3>
&lt;p>PXC ensures that encrypted data remains readable on all nodes, but it does not distribute encryption keys themselves. Each node must have access to the same key material, or encrypted tablespaces will fail to open.&lt;/p>
&lt;p>If a node starts without the correct keyring file, you may see:&lt;/p>
&lt;ul>
&lt;li>Tablespace open failures&lt;/li>
&lt;li>Startup errors related to encryption&lt;/li>
&lt;li>Inconsistent behavior during SST or IST&lt;/li>
&lt;/ul>
&lt;p>This is expected behavior and not a bug.&lt;/p>
&lt;h3 id="distribute-the-keyring-file-to-other-nodes">Distribute the Keyring File to Other Nodes&lt;/h3>
&lt;p>On each remaining PXC node:&lt;/p>
&lt;ol>
&lt;li>Ensure MySQL is stopped&lt;/li>
&lt;li>Create the keyring directory if it does not exist:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/mysql-keyring
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown mysql:mysql /var/lib/mysql-keyring
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod &lt;span class="m">750&lt;/span> /var/lib/mysql-keyring&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol>
&lt;li>Securely copy the keyring file from Node1:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">scp /var/lib/mysql-keyring/component_keyring_file node2:/var/lib/mysql-keyring/component_keyring_file&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Important:&lt;/strong>
Do not modify the file. Do not recreate it. Do not allow MySQL to generate a new one on secondary nodes.&lt;/p>
&lt;h3 id="start-mysql-on-each-node-and-verify">Start MySQL on Each Node and Verify&lt;/h3>
&lt;p>After the keyring file is in place:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl start mysqld&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify the component is active:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">keyring_component_status&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Each node should report the component_keyring_file as loaded and active.&lt;/p>
&lt;p>At this point:&lt;/p>
&lt;ul>
&lt;li>Encrypted tablespaces will open correctly&lt;/li>
&lt;li>SST and IST operations will succeed&lt;/li>
&lt;li>The cluster will behave consistently during restarts&lt;/li>
&lt;/ul>
&lt;h2 id="operational-notes-and-best-practices">Operational Notes and Best Practices&lt;/h2>
&lt;ul>
&lt;li>Treat the keyring file like a secret, not configuration&lt;/li>
&lt;li>Restrict access to root only&lt;/li>
&lt;li>Include the keyring file in your secure backup strategy&lt;/li>
&lt;li>When provisioning new nodes, copy the keyring file before first startup&lt;/li>
&lt;li>Never rotate or regenerate the keyring independently on individual nodes&lt;/li>
&lt;/ul>
&lt;p>If the keyring is lost and encrypted data exists, recovery is not possible.&lt;/p>
&lt;hr>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>This setup works reliably for:&lt;/p>
&lt;ul>
&lt;li>Percona Server 8.4&lt;/li>
&lt;li>Percona XtraDB Cluster 8.4&lt;br>
(with the known exception of 8.4.4–8.4.5)&lt;/li>
&lt;/ul>
&lt;p>Most failures come down to:&lt;/p>
&lt;ul>
&lt;li>Treating JSON like a &lt;code>.cnf&lt;/code> file&lt;/li>
&lt;li>Loose ownership on sensitive files&lt;/li>
&lt;li>Forgetting the PXC-specific workaround&lt;/li>
&lt;/ul>
&lt;p>Once those are handled, the component keyring fades into the background where it belongs. And when it comes to encryption, boring, quiet, and uneventful is exactly the outcome you want.&lt;/p></content:encoded><author>Wayne Leutwyler</author><author>Stan Lipinski</author><category>Opensource</category><category>Percona</category><category>key ring</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>PXC</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2026/01/keyring-component_hu_55d2689b19226a60.jpg"/><media:content url="https://percona.community/blog/2026/01/keyring-component_hu_248d0e8fdd379cd5.jpg" medium="image"/></item><item><title>Open source, PostgreSQL, and risk mitigation in an era of acquisitions</title><link>https://percona.community/blog/2025/12/19/open-source-postgresql-and-risk-mitigation-in-an-era-of-acquisitions/</link><guid>https://percona.community/blog/2025/12/19/open-source-postgresql-and-risk-mitigation-in-an-era-of-acquisitions/</guid><pubDate>Fri, 19 Dec 2025 11:00:00 UTC</pubDate><description>As the year comes to a close and many of us start slowing down before the winter holidays, I find myself reflecting on patterns I’ve seen repeat, both as a customer and as someone working closely with the PostgreSQL ecosystem.</description><content:encoded>&lt;p>As the year comes to a close and many of us start slowing down before the winter holidays, I find myself reflecting on patterns I’ve seen repeat, both as a customer and as someone working closely with the PostgreSQL ecosystem.&lt;/p>
&lt;p>Looking back at software acquisitions over the past year, one might assume they only change logos. In reality, they often change roadmaps, priorities, and unfortunately all too often also the promises customers originally bought into.&lt;/p>
&lt;p>I have experienced this more than once as a customer. Most recently, my team evaluated a product management tool that, shortly after being acquired, informed us we had a single month to migrate away. While I personally had not invested much time yet, colleagues had already moved documentation and workflows only to see that work become wasted effort. For those not familiar with product management, migrating all the product planning documentation is a huge effort and having it wasted is like erasing a month of your life.&lt;/p>
&lt;p>One might say “business is business” and that customers can always take their money elsewhere. That may be true, but it does little to make customers feel safe or protected. In practice, it often leaves them confused about where accountability lies. Who should we blame when the original vendor no longer exists and the acquiring company is acting in its own strategic interest?&lt;/p>
&lt;p>What struck me this year is how often these experiences echoed each other: across different tools, teams, and ecosystems. Unfortunately, I am now seeing similar patterns emerge in my professional focus area: PostgreSQL.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2048" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_85d1757c8d639828.webp 768w, https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_433be0c7f9e4e781.webp 480w, https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_c29670fb6d2d2b95.webp 1400w" src="https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_c29670fb6d2d2b95.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>To be very clear, the PostgreSQL Community itself is driving an outstanding database, one that has been, and I am confident will remain, truly open source and community-developed. PostgreSQL as a project is healthy, stable, and thriving, and I hope it will thrive even more in the coming year.&lt;/p>
&lt;p>That said, PostgreSQL is not just the database engine itself, but an entire ecosystem of vendors, extensions, and services built around it.&lt;/p>
&lt;p>Over the past year, several important companies in the PostgreSQL ecosystem have been acquired and in conversations throughout the year this topic kept resurfacing. In recent prospective customer conversations, we increasingly hear from PostgreSQL users running mission-critical workloads on-premises who feel abandoned by their vendor. These organizations are being quietly nudged toward “modernization” paths that, in practice, resemble mandatory SaaS migration.&lt;/p>
&lt;p>Having worked closely with MongoDB users for years, this pattern feels familiar. Since MongoDB Atlas became the primary strategic focus, many customers experienced similar pressure. What feels different, and what stood out to me most this year, in the PostgreSQL world is timing.&lt;/p>
&lt;p>Teams often discover late in the renewal cycle that:&lt;/p>
&lt;ul>
&lt;li>Their on-prem PostgreSQL deployment is no longer strategic for the vendor&lt;/li>
&lt;li>Key components they depend on, which were never fully open source, are no longer available for renewal&lt;/li>
&lt;li>The implied options are to “move to the cloud” or rapidly find an alternative, even when migration planning is complicated by lack of source availability&lt;/li>
&lt;/ul>
&lt;p>This puts PostgreSQL customers in a difficult position:
accept architectural change under pressure, or scramble to replace a trusted vendor while the clock is already ticking.&lt;/p>
&lt;p>While recent conversations often reference Crunchy Data following its acquisition by Snowflake, this is not about a single company. The broader pattern has repeated across the industry, including infrastructure significant projects involving MinIO, Bitnami, HashiCorp, and Redis.&lt;/p>
&lt;p>This raises a fundamental question for the PostgreSQL ecosystem and infrastructure software in general:&lt;/p>
&lt;blockquote>
&lt;p>When a critical infrastructure vendor is acquired or changes licensing, who advocates for the customers that cannot move?&lt;/p>&lt;/blockquote>
&lt;p>Open source is not just a licensing model or philosophical ideal. It provides freedom of choice, deployment flexibility, and risk mitigation. Recent community responses such as OpenTofu, OpenBao, and Valkey demonstrate a growing maturity and ability of open source communities to organize when freedoms erode.&lt;/p>
&lt;p>It is disappointing to see these dynamics emerge around PostgreSQL, even though the database itself remains one of the most open source and community governed projects in the industry.&lt;/p>
&lt;p>It is disappointing to see similar dynamics affect PostgreSQL, a database often considered one of the most open source driven projects in the industry. When PostgreSQL derivatives are impacted, the shadow inevitably reaches upstream as well.&lt;/p>
&lt;p>If this post reaches teams who were not yet aware of these shifts and gives them more time to plan going into the new year, it serves its purpose. If you are running PostgreSQL in environments where SaaS is not an option, now is the time to ask difficult questions before renewal conversations start, not after options disappear.&lt;/p>
&lt;p>As we head into a new year, I expect these conversations to become even more common. Acquisitions, licensing changes and cloud first strategies are not slowing down, but neither is the need for predictability, transparency and choice in how PostgreSQL is deployed and supported.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2058" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/Jan-2026_hu_4c93d3ab45928c9b.webp 768w, https://percona.community/blog/2025/12/Jan-2026_hu_3ade07d841f13e0.webp 480w, https://percona.community/blog/2025/12/Jan-2026_hu_96fc147aa9841603.webp 1400w" src="https://percona.community/blog/2025/12/Jan-2026_hu_96fc147aa9841603.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Looking ahead to 2026, my hope is simple: more open source products, more stable and predictable service offerings around them, and fewer last-minute surprises for the teams who depend on them every day.&lt;/p>
&lt;h1 id="we-want-to-hear-from-you">We Want to Hear from You&lt;/h1>
&lt;p>I am curious:&lt;/p>
&lt;ul>
&lt;li>Have you seen similar shifts in PostgreSQL vendors post-acquisition?&lt;/li>
&lt;li>What is preventing you from moving to PostgreSQL community builds or alternative support models?&lt;/li>
&lt;/ul>
&lt;p>Let’s talk.
You can reach me via:&lt;/p>
&lt;ul>
&lt;li>LinkedIn: &lt;a href="https://www.linkedin.com/in/janwie/" target="_blank" rel="noopener noreferrer">https://www.linkedin.com/in/janwie/&lt;/a>&lt;/li>
&lt;li>Email: jan(dot)wieremjewicz(at)percona(dot)com&lt;/li>
&lt;/ul>
&lt;h1 id="open-source-isnt-a-strategy-its-who-we-are">Open Source isn’t a strategy, it’s who we are!&lt;/h1>
&lt;p>We are always open to your feedback. You can reach us at:&lt;/p>
&lt;ul>
&lt;li>Percona Community Forums &lt;a href="https://forums.percona.com/c/postgresql/25" target="_blank" rel="noopener noreferrer">https://forums.percona.com/c/postgresql/25&lt;/a>&lt;/li>
&lt;li>Via issues and discussions on &lt;a href="https://github.com/percona/" target="_blank" rel="noopener noreferrer">Percona GitHub repositories&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>If there is an event we attend, focused on open source or not, don’t be a stranger, come chat with us in person!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/12/Jan-locked-slonik-cover_hu_9495f13a229ac220.jpg"/><media:content url="https://percona.community/blog/2025/12/Jan-locked-slonik-cover_hu_1142c98fc8d1ada8.jpg" medium="image"/></item><item><title>Enhancing PostgreSQL OIDC with pg_oidc_validator</title><link>https://percona.community/blog/2025/12/17/enhancing-postgresql-oidc-with-pg_oidc_validator/</link><guid>https://percona.community/blog/2025/12/17/enhancing-postgresql-oidc-with-pg_oidc_validator/</guid><pubDate>Wed, 17 Dec 2025 11:00:00 UTC</pubDate><description>With PostgreSQL 18 introducing built-in OAuth 2.0 and OpenID Connect (OIDC) authentication, tools like pg_oidc_validator have become an essential part of the ecosystem by enabling server-side verification of OIDC tokens directly inside PostgreSQL. If you’re new to the topic, make sure to read our earlier posts explaining the underlying concepts and the need for external validators:</description><content:encoded>&lt;p>With PostgreSQL 18 introducing built-in OAuth 2.0 and OpenID Connect (OIDC) authentication, tools like &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator" target="_blank" rel="noopener noreferrer">pg_oidc_validator&lt;/a> have become an essential part of the ecosystem by enabling server-side verification of OIDC tokens directly inside PostgreSQL. If you’re new to the topic, make sure to read our earlier posts explaining the underlying concepts and the need for external validators:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://percona.community/blog/2025/11/07/oauth-oidc-validators/" target="_blank" rel="noopener noreferrer">Why PostgreSQL needs external token validators&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/" target="_blank" rel="noopener noreferrer">Security aspects of OIDC validation in PostgreSQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/blog/postgresql-oidc-authentication-with-pg_oidc_validator/" target="_blank" rel="noopener noreferrer">Deploying pg_oidc_validator v0.1 - a DBA’s perspective&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This release builds on the initial version &lt;a href="https://percona.community/blog/2025/10/22/say-hello-to-oidc-in-postgresql-18/" target="_blank" rel="noopener noreferrer">announced in October&lt;/a> and continues our mission to make OIDC adoption in PostgreSQL reliable, fast, and accessible for all users.&lt;/p>
&lt;h2 id="whats-new-in-this-release">&lt;strong>What’s New in This Release&lt;/strong>&lt;/h2>
&lt;p>This new iteration of pg_oidc_validator (v0.2) introduces two major improvements:&lt;/p>
&lt;ul>
&lt;li>initial caching support, and&lt;/li>
&lt;li>Debian/Ubuntu and RPM packages to simplify installation.&lt;/li>
&lt;/ul>
&lt;p>Most importantly, these improvements come directly from community feedback, ****whether during conversations at PGConf.EU in Riga, KubeCon US in Atlanta, or through GitHub and forums. Thank you for helping us shape this project!&lt;/p>
&lt;h2 id="caching-support-in-pg_">&lt;strong>Caching Support in pg_oidc_validator&lt;/strong>&lt;/h2>
&lt;p>OIDC token verification requires fetching issuer metadata and JWKS keyset&lt;strong>s&lt;/strong> from an external identity provider (IdP). Without caching, every PostgreSQL backend performing validation must re-fetch this data, increasing latency and putting unnecessary load on the IdP.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1024" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2025/12/pg_oidc_cache_hu_f10878caa3e7ff1b.webp 768w, https://percona.community/blog/2025/12/pg_oidc_cache_hu_6adfe110b75f5505.webp 480w, https://percona.community/blog/2025/12/pg_oidc_cache_hu_a315a4a8cf167da1.webp 1024w" src="https://percona.community/blog/2025/12/pg_oidc_cache_hu_a315a4a8cf167da1.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>pg_oidc_validator v0.2 introduces a lightweight caching layer. This allows the validator to:&lt;/p>
&lt;ul>
&lt;li>cache OIDC discovery documents and JWKS responses when permitted by the IdP,&lt;/li>
&lt;li>use cached responses across PostgreSQL backends,&lt;/li>
&lt;li>reduce outbound HTTP calls,&lt;/li>
&lt;li>validate tokens at in-memory speeds, and&lt;/li>
&lt;li>integrate cleanly with IdP key rotation.&lt;/li>
&lt;/ul>
&lt;p>This results in improved performance, reduced IdP load, and better scalability for deployments using Keycloak, Okta, Microsoft Entra ID, Ping Identity, or other OIDC providers.&lt;/p>
&lt;h3 id="a-note-on-testing">&lt;strong>A Note on Testing&lt;/strong>&lt;/h3>
&lt;p>The caching layer currently lacks full automated test coverage. This is because Keycloak does not allow caching for issuer or JWKS endpoints (&lt;a href="https://github.com/keycloak/keycloak/issues/15216" target="_blank" rel="noopener noreferrer">Keycloak issue #15216&lt;/a>), preventing us from validating caching behavior.&lt;/p>
&lt;p>To address this, we plan to extend the test setup by placing an nginx proxy between PostgreSQL and Keycloak to simulate IdP responses that include cache-friendly headers.&lt;/p>
&lt;h2 id="pre-built-packages-now-available">&lt;strong>Pre-Built Packages Now Available&lt;/strong>&lt;/h2>
&lt;p>Installing pg_oidc_validator is now easier than ever. We provide builds at the &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/releases/tag/latest" target="_blank" rel="noopener noreferrer">latest release page&lt;/a>, where nightly builds are available for:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Debian / Ubuntu&lt;/strong> - tested on Ubuntu 24.04&lt;/li>
&lt;li>&lt;strong>RHEL / Oracle Linux / Rocky Linux&lt;/strong> - tested on OL8 and OL9&lt;/li>
&lt;/ul>
&lt;p>If you prefer building from source, instructions are available directly in the project’s &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator" target="_blank" rel="noopener noreferrer">README&lt;/a>.&lt;/p>
&lt;h2 id="try-it-test-it-tell-us-all-about-it">&lt;strong>Try it, test it, tell us all about it!&lt;/strong>&lt;/h2>
&lt;p>As an open source project, pg_oidc_validator grows with your feedback. We want to hear about:&lt;/p>
&lt;ul>
&lt;li>your deployment use cases,&lt;/li>
&lt;li>performance characteristics,&lt;/li>
&lt;li>integration challenges,&lt;/li>
&lt;li>and features you’d like to see next.&lt;/li>
&lt;/ul>
&lt;p>You can reach us here:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Percona Community Forums:&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://forums.percona.com/c/postgresql/25" target="_blank" rel="noopener noreferrer">https://forums.percona.com/c/postgresql/25&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>GitHub:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Issues: &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/issues" target="_blank" rel="noopener noreferrer">https://github.com/Percona-Lab/pg_oidc_validator/issues&lt;/a>&lt;/li>
&lt;li>Discussions: &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/discussions" target="_blank" rel="noopener noreferrer">https://github.com/Percona-Lab/pg_oidc_validator/discussions&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>And of course, if you see Percona at an event, come talk to us at the booth!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_oidc_validator</category><category>security</category><category>OIDC</category><media:thumbnail url="https://percona.community/blog/2025/12/Jan-OIDC-val-cover_hu_c8240695d0132996.jpg"/><media:content url="https://percona.community/blog/2025/12/Jan-OIDC-val-cover_hu_86489a51894997f3.jpg" medium="image"/></item><item><title>What is New in Percona Toolkit 3.7.1</title><link>https://percona.community/blog/2025/12/17/what-is-new-in-percona-toolkit-3.7.1/</link><guid>https://percona.community/blog/2025/12/17/what-is-new-in-percona-toolkit-3.7.1/</guid><pubDate>Wed, 17 Dec 2025 00:00:00 UTC</pubDate><description>Percona Toolkit 3.7.1 has been released on Dec 17, 2025. The most important updates in this version are:</description><content:encoded>&lt;p>Percona Toolkit 3.7.1 has been released on &lt;strong>Dec 17, 2025&lt;/strong>. The most important updates in this version are:&lt;/p>
&lt;ul>
&lt;li>Finalized SSL/TLS support for MySQL&lt;/li>
&lt;li>Added support for Debian 13 and Amazon Linux 2023&lt;/li>
&lt;li>Fixed MariaDB support broken in version 3.7.0&lt;/li>
&lt;li>Added options to skip certain collections in &lt;code>pt-k8s-debug-collector&lt;/code> and &lt;code>pt-stalk&lt;/code>&lt;/li>
&lt;li>Documentation improvements&lt;/li>
&lt;li>Other performance improvements&lt;/li>
&lt;/ul>
&lt;p>In this blog, I will outline the most significant changes. A full list of improvements and bug fixes can be found in the &lt;a href="https://docs.percona.com/percona-toolkit/release_notes.html" target="_blank" rel="noopener noreferrer">release notes&lt;/a>.&lt;/p>
&lt;h1 id="ssltls-support-for-mysql">SSL/TLS support for MySQL&lt;/h1>
&lt;p>Percona Toolkit historically did not have consistent SSL support. This was reported at &lt;a href="https://perconadev.atlassian.net/browse/PT-191" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PT-191&lt;/a>. In version 3.7.0, option &lt;code>s&lt;/code> for &lt;code>DSN&lt;/code> was introduced. This option instructs &lt;code>DBD::mysql&lt;/code> to open a secure connection with the database. This version also adds command-line option &lt;code>--mysql-ssl&lt;/code> and its short form &lt;code>-s&lt;/code> to all tools. All other SSL/TLS-related options, such as &lt;code>ssl-ca&lt;/code>, &lt;code>ssl-cert&lt;/code>, &lt;code>ssl-cipher&lt;/code>, etc, could be specified in the configuration file if necessary. This completes SSL/TLS support for MySQL. For more details and information check &lt;a href="https://www.percona.com/blog/unlocking-secure-connections-ssl-tls-support-in-percona-toolkit/" target="_blank" rel="noopener noreferrer">this blog post&lt;/a>.&lt;/p>
&lt;h1 id="supported-platforms-update">Supported Platforms Update&lt;/h1>
&lt;p>Percona repositories now have Percona Toolkit packages for Debian 13 and Amazon Linux 2023. To install them enable repository &lt;code>pt&lt;/code> with the &lt;code>percona-release&lt;/code> utility. Ubuntu Focal reached its EOL and support for this platform has been removed. More information on Percona repositories is available in the &lt;a href="https://docs.percona.com/percona-software-repositories/index.html" target="_blank" rel="noopener noreferrer">User Reference Manual&lt;/a>.&lt;/p>
&lt;h1 id="regression-bug-fixes">Regression Bug Fixes&lt;/h1>
&lt;p>Recent major changes introducing MySQL 8.4 support missed ignore case modificator for the regular expression that checks if MySQL flavor is MariaDB. As a result, tools executed replication statements not compatible with MariaDB. Version 3.7.1 fixes the regular expression and re-adds MariaDB support back (&lt;a href="https://perconadev.atlassian.net/browse/PT-2451" target="_blank" rel="noopener noreferrer">PT-2451&lt;/a>). Future versions of Percona Toolkit will have better MariaDB support, including MariaDB-specific versions of non-offensive replication commands.&lt;/p>
&lt;p>Utility &lt;code>pt-sift&lt;/code> stopped working, because dependent library &lt;code>alt_cmds.sh&lt;/code> was not included (&lt;a href="https://perconadev.atlassian.net/browse/PT-2498" target="_blank" rel="noopener noreferrer">PT-2498&lt;/a>). This was not found during previous release testing, because regression test for the tool was not run. Now this miss is fixed and the utility works properly again. Additionally, regression test is updated.&lt;/p>
&lt;p>Helper utility &lt;code>version_cmp&lt;/code> was written in some compiled language and source code for it was not available (&lt;a href="https://perconadev.atlassian.net/browse/PT-2469" target="_blank" rel="noopener noreferrer">PT-2469&lt;/a>). This broke version checking on platforms not compatible with the unknown platform where the binary was originally compiled. Now this utility rewritten as a Bourne-Again shell script.&lt;/p>
&lt;h1 id="modern-mysql-support">Modern MySQL Support&lt;/h1>
&lt;p>Percona Toolkit uses legacy MySQL syntax in many places to be compatible with older versions of MySQL. In other places, it misses modern MySQL diagnostic additions. This version makes first steps to improve this situation by adding such features as invisible index support in &lt;code>pt-duplicate-key-checker&lt;/code> (&lt;a href="github.com/percona/percona-toolkit/pull/996">PR-996&lt;/a>) and &lt;code>performance_schema.threads&lt;/code> collecton in &lt;code>pt-stalk&lt;/code> (&lt;a href="https://perconadev.atlassian.net/browse/PT-1718" target="_blank" rel="noopener noreferrer">PT-1718&lt;/a>). Currently, data from &lt;code>performance_schema.threads&lt;/code> is collected along with the deprecated &lt;code>information_schema.processlist&lt;/code>. In the future, support for &lt;code>information_schema.processlist&lt;/code> will be deprecated, then removed.&lt;/p>
&lt;p>Future versions of Percona Toolkit will have more modern MySQL diagnostic support.&lt;/p>
&lt;h1 id="performance-improvements">Performance Improvements&lt;/h1>
&lt;p>&lt;code>pt-stalk&lt;/code> now has new option, &lt;code>--skip-collection&lt;/code>, that allows to skip one or more collections. Supported values for this option are: &lt;code>ps-locks-transactions&lt;/code>, &lt;code>thread-variables&lt;/code>, &lt;code>innodbstatus&lt;/code>, &lt;code>lock-waits&lt;/code>, &lt;code>mysqladmin&lt;/code>, &lt;code>processlist&lt;/code>, &lt;code>rocksdbstatus&lt;/code>, &lt;code>transactions&lt;/code>. To skip two or more collections, separate them with a comma. E.g., &lt;code>--skip-collection=processlist,innodbstatus&lt;/code>. You will find more information at &lt;a href="https://perconadev.atlassian.net/browse/PT-2289" target="_blank" rel="noopener noreferrer">PT-2289&lt;/a> and in the &lt;a href="https://docs.percona.com/percona-toolkit/pt-stalk.html" target="_blank" rel="noopener noreferrer">User Reference Manual for &lt;code>pt-stalk&lt;/code>&lt;/a>.&lt;/p>
&lt;p>&lt;code>pt-k8s-debug-collector&lt;/code> introduces option &lt;code>-skip-pod-summary&lt;/code> allowing to skip pod summary collections, such as &lt;code>pt-mysql-summary&lt;/code>, &lt;code>pt-mongodb-summary&lt;/code>, or &lt;code>pg_gather&lt;/code>. Check &lt;a href="https://perconadev.atlassian.net/browse/PT-2453" target="_blank" rel="noopener noreferrer">PT-2453&lt;/a> and the &lt;a href="https://docs.percona.com/percona-toolkit/pt-k8s-debug-collector.html" target="_blank" rel="noopener noreferrer">User Reference Manual for &lt;code>pt-k8s-debug-collector&lt;/code>&lt;/a>.&lt;/p>
&lt;p>Originally, tools output was always buffered. This is usually good for performance but you may want to disable this feature when need to see output of the tools faster. For example, if you run &lt;code>pt-archiver&lt;/code> or &lt;code>pt-table-checksum&lt;/code> on large table in Kubernetes, you won’t see progress (&lt;a href="https://perconadev.atlassian.net/browse/PT-2052" target="_blank" rel="noopener noreferrer">PT-2052&lt;/a>) until the tool finishes. New option, &lt;code>--[no]buffer-stdout&lt;/code>, allows to disable buffering when needed.&lt;/p>
&lt;h2 id="incompatilbe-change">Incompatilbe change&lt;/h2>
&lt;p>Earlier, if &lt;code>--chunk-size&lt;/code> was enabled for &lt;code>pt-online-schema-change&lt;/code>, option &lt;code>--chunk-time&lt;/code> was ignored. This caused situations when a user has to start with default automatic chunk size even if it was not effective for some tables, and wait when chunk size is adjusted in subsequent iterations. Alternatively, they had to guess fixed chunk size that implies time consuming &lt;a href="https://en.wikipedia.org/wiki/Trial_and_error" target="_blank" rel="noopener noreferrer">try and error&lt;/a> approach (&lt;a href="https://perconadev.atlassian.net/browse/PT-1423" target="_blank" rel="noopener noreferrer">PT-1423&lt;/a>).&lt;/p>
&lt;p>Starting from version 3.7.1, if both options &lt;code>--chunk-size&lt;/code> and &lt;code>--chunk-time&lt;/code> are specified, initial chunk size will be as specified by the option &lt;code>--chunk-size&lt;/code>, but later it will be adjusted, so that the next query takes specified amount of time (in seconds) to execute.&lt;/p>
&lt;h1 id="documentation-improvements">Documentation Improvements&lt;/h1>
&lt;p>While working on this release we found undocumented featues such as &lt;code>--recursion-method=dsn&lt;/code> support in &lt;code>pt-table-sync&lt;/code> (&lt;a href="https://perconadev.atlassian.net/browse/PT-2470" target="_blank" rel="noopener noreferrer">PT-2470&lt;/a>), broken man page for &lt;code>pt-secure-collect&lt;/code> and other tools written in Go language (&lt;a href="https://perconadev.atlassian.net/browse/PT-1564" target="_blank" rel="noopener noreferrer">PT-1564&lt;/a>), as well as minor documentation issues. Now all of them are fixed.&lt;/p>
&lt;h1 id="community-contributions">Community contributions&lt;/h1>
&lt;p>This release includes contributions from Community and Percona Engineers who do not actively work on the project. We want to thank:&lt;/p>
&lt;ul>
&lt;li>Iwo Panowicz for option &lt;code>-skip-pod-summary&lt;/code> in &lt;code>pt-k8s-debug-collector&lt;/code> (&lt;a href="https://perconadev.atlassian.net/browse/PT-2453" target="_blank" rel="noopener noreferrer">PT-2453&lt;/a>)&lt;/li>
&lt;li>Matthew Boehm for invisible indexes support in &lt;code>pt-duplicate-key-checker&lt;/code> (&lt;a href="https://github.com/percona/percona-toolkit/pull/996" target="_blank" rel="noopener noreferrer">PR-996&lt;/a>)&lt;/li>
&lt;li>Nilnandan Joshi for collecting &lt;code>performance_schema.threads&lt;/code> along with &lt;code>information_schema.processlist&lt;/code> in &lt;code>pt-stalk&lt;/code> (&lt;a href="https://perconadev.atlassian.net/browse/PT-1718" target="_blank" rel="noopener noreferrer">PT-1718&lt;/a>) and fix for &lt;a href="https://perconadev.atlassian.net/browse/PT-2014" target="_blank" rel="noopener noreferrer">PT-2014 - pt-config-diff does not honor case insensitivity flag&lt;/a>&lt;/li>
&lt;li>Paweł Kudzia for the updated documentation of pt-query-digest (&lt;a href="https://github.com/percona/percona-toolkit/pull/953" target="_blank" rel="noopener noreferrer">PR-953&lt;/a>)&lt;/li>
&lt;li>Maciej Dobrzanski for fixing &lt;a href="https://github.com/percona/percona-toolkit/pull/890" target="_blank" rel="noopener noreferrer">PR-890 - pt-config-diff: MySQL truncates run-time variable values longer than 1024 characters&lt;/a>&lt;/li>
&lt;li>Marek Knappe for fixing &lt;a href="https://perconadev.atlassian.net/browse/PT-2418" target="_blank" rel="noopener noreferrer">PT-2418 - pt-online-schema-change 3.7.0 lost data when exe alter xxx rename column xxx&lt;/a> and &lt;a href="https://perconadev.atlassian.net/browse/PT-2458" target="_blank" rel="noopener noreferrer">PT-2458 - remove-data-dir defaults to True&lt;/a>&lt;/li>
&lt;li>Yoann La Cancellera for his work on &lt;code>pt-galera-log-explainer&lt;/code>&lt;/li>
&lt;li>Nyele for restoring MariaDB support (&lt;a href="https://perconadev.atlassian.net/browse/PT-2465" target="_blank" rel="noopener noreferrer">PT-2465&lt;/a>)&lt;/li>
&lt;li>Taehyung Lim for fixing &lt;a href="https://perconadev.atlassian.net/browse/PT-2401" target="_blank" rel="noopener noreferrer">PT-2401 - pt-online-schema-change ’table does not exist’ on macos&lt;/a>&lt;/li>
&lt;li>Viktoras Agejevas for fixing &lt;a href="https://github.com/percona/percona-toolkit/pull/989" target="_blank" rel="noopener noreferrer">PR-989 - Fix script crashing with precedence error&lt;/a> in &lt;code>pt-online-schema-change&lt;/code>&lt;/li>
&lt;li>Hartley McGuire for fixing &lt;a href="https://perconadev.atlassian.net/browse/PT-2015" target="_blank" rel="noopener noreferrer">PT-2015 - pt-config-diff does not sort variable flags&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Sveta Smirnova</author><category>Toolkit</category><category>MySQL</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/12/toolkit-371_hu_976a247c7fe02282.jpg"/><media:content url="https://percona.community/blog/2025/12/toolkit-371_hu_bcdcfd45e2eeedce.jpg" medium="image"/></item><item><title>MySQL Replication Best Practices: How to Keep Your Replicas Sane (and Your Nights Quiet)</title><link>https://percona.community/blog/2025/12/03/mysql-replication-best-practices-how-to-keep-your-replicas-sane-and-your-nights-quiet/</link><guid>https://percona.community/blog/2025/12/03/mysql-replication-best-practices-how-to-keep-your-replicas-sane-and-your-nights-quiet/</guid><pubDate>Wed, 03 Dec 2025 00:00:00 UTC</pubDate><description>MySQL replication has been around forever, and yet… people still manage to set it up in ways that break at the worst possible moment. Even in 2025, you can get burned by tiny schema differences, missing primary keys, or one forgotten config flag. I’ve seen replicas drift so far out of sync they might as well live in a different universe.</description><content:encoded>&lt;p>MySQL replication has been around forever, and yet… people still manage to set it up in ways that break at the worst possible moment. Even in 2025, you can get burned by tiny schema differences, missing primary keys, or one forgotten config flag. I’ve seen replicas drift so far out of sync they might as well live in a different universe.&lt;/p>
&lt;p>This guide covers the practical best practices—the stuff real DBAs use every day to keep replication stable, predictable, and boring. (Boring is a compliment in database land.)&lt;/p>
&lt;h3 id="always-use-gtids-yes-always">Always Use GTIDs. Yes, Always.&lt;/h3>
&lt;p>GTID-based replication is one of those features that people resist turning on, and then once they do, they never want to go back.&lt;/p>
&lt;p>Why GTIDs?&lt;/p>
&lt;ul>
&lt;li>Failover become sane&lt;/li>
&lt;li>Reparenting replicas stops being a headache&lt;/li>
&lt;li>Missing transactions are easy to detect&lt;/li>
&lt;/ul>
&lt;p>Your my.cnf should absolutely include:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">gtid_mode=ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">enforce_gtid_consistency=ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log_replica_updates=ON&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once GTIDs are enabled, do not mix in old-style replication. That path leads straight to confusion.&lt;/p>
&lt;h3 id="use-row-based-replication-rbr">Use Row-Based Replication (RBR)&lt;/h3>
&lt;p>Statement-based replication is a nostalgia trip that nobody asked for. It breaks on:&lt;/p>
&lt;ul>
&lt;li>NOW(), UUID(), and similar functions&lt;/li>
&lt;li>Floating point differences&lt;/li>
&lt;li>Collation mismatches&lt;/li>
&lt;li>Triggers behaving differently&lt;/li>
&lt;/ul>
&lt;p>Just skip the pain and use:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">binlog_format=ROW&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>RBR is slightly more verbose, but 100× more predictable. When something breaks, it’s never because you chose ROW.&lt;/p>
&lt;h3 id="every-table-needs-a-primary-key-no-exceptions">Every Table Needs a Primary Key. No Exceptions.&lt;/h3>
&lt;p>If you take nothing else from this guide, take this:&lt;/p>
&lt;p>&lt;strong>Replication without primary keys is a bad time.&lt;/strong>&lt;/p>
&lt;p>Row-based replication needs a way to find the row that changed. Without a PK (or at least a UNIQUE index), the server has to use every column as a lookup. That’s slow, error-prone, and sometimes impossible.&lt;/p>
&lt;p>The usual symptoms:&lt;/p>
&lt;ul>
&lt;li>Replication lag slowly creeping up&lt;/li>
&lt;li>Replica doing full table scans on updates&lt;/li>
&lt;li>Rows failing to apply&lt;/li>
&lt;li>Errors like:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Error 1032: Can't find record in table&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Save yourself hours of debugging and just make sure every table has a primary key.&lt;/p>
&lt;h3 id="keep-the-schema-identical-everywhere">Keep the Schema Identical Everywhere&lt;/h3>
&lt;p>Replication assumes that everyone’s using the same schema. MySQL will happily keep going even if your schemas don’t match—and then quietly drift out of sync.&lt;/p>
&lt;p>Here are the practical ways to keep schemas aligned:&lt;/p>
&lt;h4 id="approach-a--mysqldump-most-common">Approach A — mysqldump (most common)&lt;/h4>
&lt;p>Export schemas only:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysqldump --no-data mydb > schema.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From both servers, then:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">diff source-schema.sql replica-schema.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="approach-b--information_schema-metadata">Approach B — information_schema metadata&lt;/h4>
&lt;p>This approach is great for automaton:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT table_name, column_name, column_type, is_nullable, column_default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM information_schema.columns
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE table_schema = 'mydb'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY table_name, ordinal_position;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Execute this query on each server and diff the results. Update mydb to match the database whose schema metadata you want to examine.&lt;/p>
&lt;h4 id="approach-c--pt-table-checksum-data-only">Approach C — pt-table-checksum (data only)&lt;/h4>
&lt;p>This doesn’t compare schemas — it catches data drift.
You should consider running it on a schedule such as:&lt;/p>
&lt;ul>
&lt;li>high-change OLTP DBs run weekly or even daily&lt;/li>
&lt;li>huge multi-TB DBs run quarterly&lt;/li>
&lt;li>some sensitive systems avoid running it during peak hours&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-table-checksum --replicate=percona.checksums&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can fix drift with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-table-sync --execute --replicate=percona.checksums&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Schema checks + data checks = safe replication.&lt;/p>
&lt;h3 id="harden-your-binary-log-settings">Harden Your Binary Log Settings&lt;/h3>
&lt;p>Your binlogs are the backbone of replication. Treat them carefully.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sync_binlog=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">binlog_row_image=FULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">binlog_expire_logs_seconds=604800 # 7 days&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>sync_binlog=1 is the big one—without it, a crash can corrupt binlogs or the GTID position, and that leads to a very bad day.&lt;/p>
&lt;h3 id="protect-your-replicas-with-super_read_only">Protect Your Replicas with super_read_only&lt;/h3>
&lt;p>Never allow accidental writes to replicas, in your my.cnf set:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">read_only=ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">super_read_only=ON&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>super_read_only&lt;/strong> closes the loophole that even SUPER users could previously use to write to replicas.&lt;/p>
&lt;h3 id="use-a-dedicated-replication-user">Use a Dedicated Replication User&lt;/h3>
&lt;p>Give the minimal permissions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE USER 'repl'@'%' IDENTIFIED BY 'strong_password';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GRANT REPLICATION REPLICA ON *.* TO 'repl'@'%';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This user should do exactly one thing: replicate.
Don’t reuse app users—you’re just begging for trouble.&lt;/p>
&lt;h3 id="replication-lag-watch-it-like-a-hawk">Replication Lag: Watch It Like a Hawk&lt;/h3>
&lt;p>Seconds_Behind_Source lies more often than you’d expect. It’s okay for a quick glance but don’t rely on it.&lt;/p>
&lt;p>Better options:&lt;/p>
&lt;ul>
&lt;li>Performance Schema: replication_applier_status_by_worker&lt;/li>
&lt;li>Percona Monitoring and Management (PMM)&lt;/li>
&lt;li>Custom heartbeat tables&lt;/li>
&lt;li>pt-heartbeat&lt;/li>
&lt;/ul>
&lt;p>Lag is one of the biggest causes of outages—monitor it continuously. Lag is usually the first sign something is wrong—catch it early.&lt;/p>
&lt;h3 id="use-parallel-replication-but-dont-overdo-it">Use Parallel Replication (But Don’t Overdo It)&lt;/h3>
&lt;p>If your primary has multiple writers or many concurrent transactions, in your my.cnf enable parallel workers:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">replica_parallel_type=LOGICAL_CLOCK
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replica_parallel_workers=4&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>4–8 workers is a sweet spot for most systems. More workers ≠ more speed; after a point it just increases memory footprint without real benefit.&lt;/p>
&lt;p>But when it helps, it really helps—like cutting lag by 80–90%.&lt;/p>
&lt;h3 id="use-ssl-anywhere-outside-the-lan">Use SSL Anywhere Outside the LAN&lt;/h3>
&lt;p>Replication traffic isn’t something you want exposed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source_ssl=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">source_ssl_ca=/path/ca.pem&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Earlier versions used the master_ssl_* variables, but the idea is the same: encrypt the connection when it leaves your trusted network.&lt;/p>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>MySQL replication can be rock-solid, but only if you follow a handful of rules that experienced DBAs know by heart:&lt;/p>
&lt;ul>
&lt;li>Use GTIDs&lt;/li>
&lt;li>Use RBR&lt;/li>
&lt;li>Always have primary keys&lt;/li>
&lt;li>Keep schemas aligned&lt;/li>
&lt;li>Check for data drift&lt;/li>
&lt;li>Harden binlog settings&lt;/li>
&lt;li>Protect replicas from accidental writes&lt;/li>
&lt;li>Monitor lag properly&lt;/li>
&lt;li>Use parallel workers when appropriate&lt;/li>
&lt;li>Encrypt connections over untrusted networks&lt;/li>
&lt;/ul>
&lt;p>Follow these, and your replicas will stay healthy, consistent, and (mostly) invisible—which is exactly how you want them.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Percona</category><category>replication</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>toolkit</category><media:thumbnail url="https://percona.community/blog/2025/12/mysql-replication-best-practice_hu_fdef2ac6195fc52b.jpg"/><media:content url="https://percona.community/blog/2025/12/mysql-replication-best-practice_hu_401bb1385cddcf3a.jpg" medium="image"/></item><item><title>Community Recap: Percona.Connect London 2025, Building the Future of Open Source Together</title><link>https://percona.community/blog/2025/12/02/community-recap-percona.connect-london-2025-building-the-future-of-open-source-together/</link><guid>https://percona.community/blog/2025/12/02/community-recap-percona.connect-london-2025-building-the-future-of-open-source-together/</guid><pubDate>Tue, 02 Dec 2025 00:00:00 UTC</pubDate><description>Percona.Connect London 2025 brought the open-source database community together for a half-day of learning and collaboration. The event focused on providing practical, technical insights for DBAs, DevOps engineers, and developers. The main takeaway was clear: Stability, Openness, and Automation are essential for modern, large-scale data infrastructure.</description><content:encoded>&lt;p>&lt;a href="https://connect.percona.com/london/" target="_blank" rel="noopener noreferrer">Percona.Connect London 2025&lt;/a> brought the open-source database community together for a half-day of learning and collaboration. The event focused on providing practical, technical insights for DBAs, DevOps engineers, and developers. The main takeaway was clear: Stability, Openness, and Automation are essential for modern, large-scale data infrastructure.&lt;/p>
&lt;h2 id="top-discussions--key-takeaways">Top Discussions &amp; Key Takeaways&lt;/h2>
&lt;h2 id="1-the-rise-of-valkey-a-truly-open-caching-alternative">1. The Rise of Valkey: A Truly Open Caching Alternative&lt;/h2>
&lt;p>&lt;strong>Martin Visser&lt;/strong>, Valkey Technical Lead, explained the changes to the Redis license, the community needs a trusted, open-source replacement. Valkey was highlighted as the leading solution.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/valkey-io/valkey" target="_blank" rel="noopener noreferrer">Valkey&lt;/a> was started by former Redis contributors quickly after Redis removed its open source license in 2024.&lt;/li>
&lt;li>It is a true open-source project governed under the Linux Foundation.&lt;/li>
&lt;li>It offers enhancements like better memory efficiency, performance, and scalability.&lt;/li>
&lt;li>In a recent Percona survey of 200 DBAs, &lt;strong>Valkey was the most preferred alternative to Redis&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1249" height="707" sizes="(max-width: 1249px) 100vw, 1249px" srcset="https://percona.community/blog/2025/12/img1_hu_46fb54392d511adf.webp 768w, https://percona.community/blog/2025/12/img1_hu_e4b5c3289c8176af.webp 480w, https://percona.community/blog/2025/12/img1_hu_de67e465bf009964.webp 1249w" src="https://percona.community/blog/2025/12/img1_hu_de67e465bf009964.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="2-running-postgresql-in-a-cloud-native-context">2. Running PostgreSQL in a Cloud Native context&lt;/h2>
&lt;p>&lt;strong>Takis Stathopoulos&lt;/strong>, Enterprise Architect, presented on running PostgreSQL in a Cloud Native context, explaining how Kubernetes Operators simplify complex deployments.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cloud Native vs. Cloud First&lt;/strong>: Cloud Native (Kubernetes) offers Portability and No vendor lock-in, allowing you to run the database consistently across different clouds and on-premise infrastructure.&lt;/li>
&lt;li>&lt;strong>Percona Operator for PostgreSQL&lt;/strong>: This tool automates crucial operations like setting up high availability (using Patroni), backups (using pgBackrest), and scaling.&lt;/li>
&lt;li>&lt;strong>When to use Cloud Native&lt;/strong>: It’s ideal for large, microservice-based applications and teams prioritizing portability and avoiding vendor lock-in.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1273" height="709" sizes="(max-width: 1273px) 100vw, 1273px" srcset="https://percona.community/blog/2025/12/img2_hu_78fff294f88b5495.webp 768w, https://percona.community/blog/2025/12/img2_hu_b93a54fdc8fd070f.webp 480w, https://percona.community/blog/2025/12/img2_hu_234370e37e7b7e5a.webp 1273w" src="https://percona.community/blog/2025/12/img2_hu_234370e37e7b7e5a.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1268" height="714" sizes="(max-width: 1268px) 100vw, 1268px" srcset="https://percona.community/blog/2025/12/img3_hu_93685942f9f7b399.webp 768w, https://percona.community/blog/2025/12/img3_hu_1026512fb0019656.webp 480w, https://percona.community/blog/2025/12/img3_hu_4ed76eaedcdfccab.webp 1268w" src="https://percona.community/blog/2025/12/img3_hu_4ed76eaedcdfccab.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="3-native-postgresql-tde-is-here-securing-data-simply">3. Native PostgreSQL TDE is Here: Securing Data Simply&lt;/h2>
&lt;p>&lt;strong>Alastair Turner&lt;/strong>, Postgres Community Advocate, introduced the new Native Transparent Data Encryption (TDE) for PostgreSQL.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2890" height="1600" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/extra_hu_5cf0c9741730eca2.webp 768w, https://percona.community/blog/2025/12/extra_hu_1187a5b72b402d20.webp 480w, https://percona.community/blog/2025/12/extra_hu_80a596e8b140f820.webp 1400w" src="https://percona.community/blog/2025/12/extra_hu_80a596e8b140f820.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="1441" height="807" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/img4_hu_69c76423943dce4.webp 768w, https://percona.community/blog/2025/12/img4_hu_b548dab28f1ca3e6.webp 480w, https://percona.community/blog/2025/12/img4_hu_93f8d9b3dd6d8b94.webp 1400w" src="https://percona.community/blog/2025/12/img4_hu_93f8d9b3dd6d8b94.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="4-the-future-of-mysql-vector-search--binlog-server">4. The Future of MySQL: Vector Search &amp; Binlog Server&lt;/h2>
&lt;p>&lt;strong>Dennis Kittrell&lt;/strong>, MySQL Product Manager, discussed two key features planned for MySQL that address major operational and feature challenges.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MySQL Binlog Server MVP&lt;/strong>: This component aims to solve the problem of quick disaster recovery by acting as a stable, reliable replication source. It enables Precise Point-in-Time Recovery (PITR) using simple time or GTID coordinates.&lt;/li>
&lt;li>&lt;strong>Native Vector Support MVP&lt;/strong>: This feature allows users to eliminate the complexity of using a separate vector database. You can store, index, and search vector embeddings directly in MySQL, allowing you to combine vector searches with standard business logic in a single, transactional query&lt;/li>
&lt;/ul>
&lt;h3 id="our-community-focus">Our Community Focus&lt;/h3>
&lt;p>A common theme from the use cases was that while open source adoption is high, operational teams often lack the proper support and visibility.&lt;/p>
&lt;p>Percona’s goal is to support the community by providing:&lt;/p>
&lt;ul>
&lt;li>Stability when under heavy load or during maintenance.&lt;/li>
&lt;li>Faster Troubleshooting with better monitoring and observability.&lt;/li>
&lt;li>Safer Deployments through expert configuration and security support.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/img5_hu_fce98a7db24a4b9b.webp 768w, https://percona.community/blog/2025/12/img5_hu_b4a54bc33e82f444.webp 480w, https://percona.community/blog/2025/12/img5_hu_aac22caf4b12df56.webp 1400w" src="https://percona.community/blog/2025/12/img5_hu_aac22caf4b12df56.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Thank you to everyone who joined us in London for a dynamic event. We hope the insights gained will help you with your open source database deployments.&lt;/p>
&lt;p>The conversations continue in the Percona Community! You can reach out directly to the speakers:&lt;/p>
&lt;ul>
&lt;li>Martin Visser (Valkey Technical Lead) &lt;a href="https://www.linkedin.com/in/martinrvisser/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>&lt;/li>
&lt;li>Dennis Kittrell (MySQL Product Manager) &lt;a href="https://www.linkedin.com/in/kittrell/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>&lt;/li>
&lt;li>Alastair Turner (Postgres Community Advocate) &lt;a href="https://www.linkedin.com/in/decodableminion/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>&lt;/li>
&lt;li>Takis Stathopoulos (Enterprise Architect) &lt;a href="https://www.linkedin.com/in/pgstathopoulos/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>&lt;/li>
&lt;li>Andre Pons (Enterprise Sales Manager) &lt;a href="https://www.linkedin.com/in/andre-pons-8b4a1013/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="4019" height="2179" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/img7_hu_b5276336bc1f2390.webp 768w, https://percona.community/blog/2025/12/img7_hu_239a8f0c9b8799b5.webp 480w, https://percona.community/blog/2025/12/img7_hu_887a94578c4f2bfc.webp 1400w" src="https://percona.community/blog/2025/12/img7_hu_887a94578c4f2bfc.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="4284" height="4010" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/12/img6_hu_48394355dc161580.webp 768w, https://percona.community/blog/2025/12/img6_hu_837d26a8dc56dc66.webp 480w, https://percona.community/blog/2025/12/img6_hu_df50e23b208c1ff7.webp 1400w" src="https://percona.community/blog/2025/12/img6_hu_df50e23b208c1ff7.webp" alt="Percona Connect London 2025" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Join the Percona Community Conversation!&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://forum.percona.com/" target="_blank" rel="noopener noreferrer">Percona Forum&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.linkedin.com/company/percona/" target="_blank" rel="noopener noreferrer">Percona on LinkedIn&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Edith Puclla</author><category>Community</category><category>Event Recap</category><category>Open Source</category><category>MySQL</category><category>PostgreSQL</category><category>Valkey</category><media:thumbnail url="https://percona.community/blog/2025/12/intro_hu_7df69ab6ed8fa3fb.jpeg"/><media:content url="https://percona.community/blog/2025/12/intro_hu_7f699ac013017a33.jpeg" medium="image"/></item><item><title>TDE is now available for PostgreSQL 18</title><link>https://percona.community/blog/2025/11/28/tde-is-now-available-for-postgresql-18/</link><guid>https://percona.community/blog/2025/11/28/tde-is-now-available-for-postgresql-18/</guid><pubDate>Fri, 28 Nov 2025 11:00:00 UTC</pubDate><description>Back in October, before PGConf.EU, I explained the issues impacting the prolonged wait for TDE in PostgreSQL 18. Explanations were needed as users were buzzing with anticipation, and they deserved to understand what caused the delays and what the roadmap looked like. In that blog post I have shared that due to one of the features newly added in 18.0, the Asynchronous IO (AIO), we have decided to give ourselves time until 18.1 has been released to provide a build with TDE. We wanted to ensure best quality of the solution and that takes time.</description><content:encoded>&lt;p>Back in October, before &lt;a href="http://pgconf.eu/" target="_blank" rel="noopener noreferrer">PGConf.EU&lt;/a>, I &lt;a href="https://percona.community/blog/2025/10/15/keep-calm-tde-for-postgresql-18-is-on-its-way/" target="_blank" rel="noopener noreferrer">explained the issues impacting the prolonged wait for TDE in PostgreSQL 18&lt;/a>. Explanations were needed as users were buzzing with anticipation, and they deserved to understand what caused the delays and what the roadmap looked like. In that blog post I have shared that due to one of the features newly added in 18.0, the &lt;a href="https://www.postgresql.org/about/featurematrix/detail/asynchronous-io-aio/" target="_blank" rel="noopener noreferrer">Asynchronous IO (AIO)&lt;/a>, we have decided to give ourselves time until 18.1 has been released to provide a build with TDE. We wanted to ensure best quality of the solution and that takes time.&lt;/p>
&lt;p>As planned in the &lt;a href="https://www.postgresql.org/developer/roadmap/" target="_blank" rel="noopener noreferrer">PostgreSQL Development Group (PGDG) roadmap&lt;/a>, on November 13, the &lt;a href="https://www.postgresql.org/about/news/postgresql-181-177-1611-1515-1420-and-1323-released-3171/" target="_blank" rel="noopener noreferrer">Community released PostgreSQL 18.1&lt;/a>, and Percona engineers managed not only to deliver TDE compatible with the PostgreSQL 18 changes, but to fully support them.
We are also skipping PostgreSQL 18.0 entirely in our distribution.
The first TDE enabled release will be &lt;a href="https://docs.percona.com/postgresql/18/release-notes/release-notes-v18.1.1.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18.1.1&lt;/a>, aligning our builds directly with the first PostgreSQL 18 minor release.
Here are some details on what’s new!&lt;/p>
&lt;h1 id="percona-postgresql--tde--aio">Percona PostgreSQL + TDE + AIO&lt;/h1>
&lt;p>Today, we’re proud to announce that Percona now ships PostgreSQL 18.1 with fully supported TDE and AIO from the very beginning.
&lt;figure>&lt;img width="3022" height="462" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_be3fcd965fdad3d0.webp 768w, https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_dcbbf4eaf900323c.webp 480w, https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_3f7cb1c518360001.webp 1400w" src="https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_3f7cb1c518360001.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>No patching, no workarounds, no “experimental caveats.” Encryption-at-rest is not just compatible with AIO. It’s supported, integrated and ready for production deployments across the ecosystem, enabling hardened PostgreSQL environments to meet compliance requirements with confidence as beginning with PostgreSQL 18.1, Percona:&lt;/p>
&lt;ul>
&lt;li>fully supports native TDE&lt;/li>
&lt;li>ships AIO-enabled builds, aligned with Community PostgreSQL&lt;/li>
&lt;li>provides production-ready packages for enterprise deployments&lt;/li>
&lt;/ul>
&lt;p>TDE matures and pg_tde returns home
In 2026 we are planning significant investment to ensure that TDE continues to evolve for Community PostgreSQL. As part of this renewed focus, Percona is shifting pg_tde back to its dedicated home:&lt;/p>
&lt;p>➡️ &lt;a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">https://github.com/percona/pg_tde&lt;/a>&lt;/p>
&lt;p>This reflects pg_tde’s new role. With the growing number of pg_tde users, it can no longer be treated as a stopgap solution filling a gap in PostgreSQL. Instead, we want to approach it as a complementary, advanced encryption layer for the PostgreSQL 18+ era:&lt;/p>
&lt;ul>
&lt;li>a place to deliver extended capabilities for data-at-rest encryption as soon as they are ready&lt;/li>
&lt;li>a hub for integrations requested by the community&lt;/li>
&lt;li>a safe space for feedback, comments, and questions to help drive the future of TDE&lt;/li>
&lt;/ul>
&lt;p>Releasing this new version brings some real improvements, especially on the KMS integration side.&lt;/p>
&lt;h1 id="kms-improvements">KMS improvements&lt;/h1>
&lt;p>We expanded Key Management Service (KMS) capabilities based directly on user and customer feedback.
Whether it came through GitHub, Percona Community Forums, events, or direct conversations — thank you! Your input shapes the roadmap.&lt;/p>
&lt;h3 id="pg_tde-now-works-with-akeyless-kms">pg_tde now works with Akeyless KMS&lt;/h3>
&lt;p>&lt;a href="https://www.akeyless.io/" target="_blank" rel="noopener noreferrer">Akeyless&lt;/a> is gaining traction among organizations implementing zero-trust security models. pg_tde now integrates cleanly with Akeyless, enabling robust key retrieval and lifecycle management across cloud and on-prem deployments using KMIP.&lt;/p>
&lt;h3 id="hashicorp-vault--openbao-namespace-support">HashiCorp Vault &amp; OpenBao Namespace Support&lt;/h3>
&lt;p>Vault and OpenBao users can now take advantage of namespaces when storing encryption keys&lt;/p>
&lt;p>Why do namespaces matter?&lt;/p>
&lt;ul>
&lt;li>Multi-tenancy: isolate key access for teams, environments, or applications&lt;/li>
&lt;li>Security boundaries: each namespace can enforce its own authentication and audit policies&lt;/li>
&lt;li>Cleaner CI/CD: dev/staging/prod can share a consistent key path structure&lt;/li>
&lt;li>Delegation &amp; separation of duties: security teams manage root policies, while application teams manage their own namespaces&lt;/li>
&lt;/ul>
&lt;p>In large organizations, namespace support isn’t just a convenience, it’s a requirement.
OpenBao prioritized this early and &lt;a href="https://openbao.org/blog/namespaces-announcement/" target="_blank" rel="noopener noreferrer">released it back in May 2025&lt;/a>.
With pg_tde now supporting namespaces natively, PostgreSQL deployments gain enterprise-grade key management flexibility.&lt;/p>
&lt;h1 id="what-comes-next">What Comes Next?&lt;/h1>
&lt;p>Our commitment remains unchanged: TDE must be a first-class, accessible, community-driven feature in PostgreSQL. Share your feedback and let us achieve this! We can build the future of an open source TDE solution for PostgreSQL together with full openness and transparency!&lt;/p>
&lt;p>Winter may be coming, but no weather can stop us! Expect more from TDE soon:&lt;/p>
&lt;ul>
&lt;li>Key length configurations are coming - expect to be able to use 256-bit keys with TDE soon!&lt;/li>
&lt;li>extended KMS integrations - we’re already looking into cloud KMS support. Please share which ones you are using and what use cases you want supported!&lt;/li>
&lt;li>improvements to pg_tde encryption targets - temporary files are next to be explored followed by system catalog&lt;/li>
&lt;li>compatibility with Community PostgreSQL - we want to drive inclusion of any changes required by pg_tde in Community PostgreSQL so that the extension can run for users of all PostgreSQL builds!&lt;/li>
&lt;/ul>
&lt;p>⠀At the risk of repeating myself, the deserved highlight goes to the fact that we build based on your feedback, so don’t be strangers!&lt;/p>
&lt;h1 id="we-want-to-hear-from-you">We Want to Hear from You&lt;/h1>
&lt;p>Tell us how you’re using PostgreSQL with TDE.
Tell us what your security requirements look like.
Tell us where tooling can be improved.
You can reach us at:&lt;/p>
&lt;ul>
&lt;li>Percona Community Forums &lt;a href="https://forums.percona.com/c/postgresql/25" target="_blank" rel="noopener noreferrer">https://forums.percona.com/c/postgresql/25&lt;/a>&lt;/li>
&lt;li>pg_tde GitHub Repo &lt;a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">https://github.com/percona/pg_tde&lt;/a>&lt;/li>
&lt;li>Issues &amp; Discussions
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/pg_tde/issues" target="_blank" rel="noopener noreferrer">https://github.com/percona/pg_tde/issues&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/pg_tde/discussions" target="_blank" rel="noopener noreferrer">https://github.com/percona/pg_tde/discussions&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Or, if you see us at an event, come chat with us in person!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/11/Jan-PG-18-cover_hu_54ab0a71620623a8.jpg"/><media:content url="https://percona.community/blog/2025/11/Jan-PG-18-cover_hu_833783130582e94e.jpg" medium="image"/></item><item><title>The Right Tool for the Job</title><link>https://percona.community/blog/2025/11/24/the-right-tool-for-the-job/</link><guid>https://percona.community/blog/2025/11/24/the-right-tool-for-the-job/</guid><pubDate>Mon, 24 Nov 2025 00:00:00 UTC</pubDate><description>When I first got into woodworking, my mentor shared a piece of advice that has stuck with me ever since: “Use the right tool for the job.” You wouldn’t reach for a belt sander to flatten a board when a planer can accomplish the task faster, cleaner, and with far better results.</description><content:encoded>&lt;p>When I first got into woodworking, my mentor shared a piece of advice that has stuck with me ever since: “Use the right tool for the job.” You wouldn’t reach for a belt sander to flatten a board when a planer can accomplish the task faster, cleaner, and with far better results.&lt;/p>
&lt;p>The same principle applies in the world of database engineering. When working with MySQL or Percona Server, choosing the correct tool can be the difference between efficient diagnostics and unnecessary downtime.&lt;/p>
&lt;p>In this post, I’ll highlight several of the most practical and commonly used utilities from the Percona Toolkit. While the toolkit includes many powerful commands, I’ll focus on the ones that provide the most value in day-to-day operations, troubleshooting, and gathering actionable details for support cases.&lt;/p>
&lt;h2 id="pt-summary">PT Summary&lt;/h2>
&lt;p>A Percona Toolkit utility that provides a concise, high-level overview of a system’s hardware, OS configuration and performance-related metrics. It’s designed to quickly capture the essential details needed for diagnostics or support cases—CPU, memory, disk layout, kernel parameters and more all in a single, easy-to-read report.&lt;/p>
&lt;h3 id="example">Example&lt;/h3>
&lt;p>Run pt-summary with no arguments to generate a full system summary. When possible, run it with sudo to allow the tool to collect additional details that require elevated privileges:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo pt-summary
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Percona Toolkit System Summary Report ######################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Date | 2025-11-24 17:15:19 UTC (local TZ: EST -0500)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Hostname | pi16gb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Uptime | 41 days, 2:27, 4 users, load average: 0.00, 0.00, 0.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Platform | Linux
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Release | Debian GNU/Linux 12 (bookworm) (bookworm)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Kernel | 6.12.47+rpt-rpi-2712
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Architecture | CPU = 32-bit, OS = 64-bit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Threading | NPTL 2.36
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SELinux | No SELinux detected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Virtualized | No virtualization detected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Processor ##################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Processors | physical = 4, cores = 0, virtual = 4, hyperthreading = no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Speeds |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Models |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Caches |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Designation Configuration Size Associativity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ========================= ============================== ======== ======================
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Memory #####################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Total | 15.8G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Free | 675.0M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Used | physical = 5.3G, swap allocated = 512.0M, swap used = 0.0, virtual = 5.3G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Shared | 44.7M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Buffers | 10.6G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Caches | 10.5G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Dirty | 128 kB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> UsedRSS | 5.1G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Swappiness | 60
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DirtyPolicy | 20, 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DirtyStatus | 0, 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Locator Size Speed Form Factor Type Type Detail
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ========= ======== ================= ============= ============= ===========
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Mounted Filesystems ########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Filesystem Size Used Type Opts Mountpoint
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /dev/nvme0n1p1 510M 14%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /dev/nvme0n1p2 458G 5%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /dev/sda1 117G 16%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Disk Schedulers And Queue Size #############################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nvme0n1 | [none] 255
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sda | [mq-deadline] 60
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Disk Partitioning ##########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Kernel Inode State #########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dentry-state | 107782 98346 45 0 32304 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> file-nr | 3680 0 9223372036854775807
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inode-nr | 99614 20818
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># LVM Volumes ################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Unable to collect information
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># LVM Volume Groups ##########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Unable to collect information
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># RAID Controller ############################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Controller | No RAID controller detected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Network Config #############################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Controller | 00.0 Ethernet controller
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> FIN Timeout | 60
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Port Range | 60999
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Interface Statistics #######################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> interface rx_bytes rx_packets rx_errors tx_bytes tx_packets tx_errors
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ========= ========= ========== ========== ========== ========== ==========
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> lo 6000000000 175000 0 6000000000 175000 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> eth0 0 0 0 0 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wlan0 5000000000 30000000 0 15000000000 22500000 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Network Devices ############################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Device Speed Duplex
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ========= ========= =========
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> eth0 Unknown! Unknown!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Network Connections ########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Connections from remote IP addresses
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 192.168.1.91 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 192.168.1.251 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2603 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Connections to local IP addresses
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 192.168.1.145 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2603 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Connections to top 10 local ports
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3306 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 6011:ef0:7260:::22 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> States of connections
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ESTABLISHED 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> LISTEN 6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> TIME_WAIT 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Top Processes ##############################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 95842 root 20 0 0 0 0 I 6.7 0.0 0:00.08 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 root 20 0 169520 13088 8672 S 0.0 0.1 0:18.56 systemd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2 root 20 0 0 0 0 S 0.0 0.0 0:01.70 kthreadd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 pool_wo+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 5 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 7 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 8 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Notable Processes ##########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PID OOM COMMAND
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ? ? sshd doesn't appear to be running
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Simplified and fuzzy rounded vmstat (wait please) ##########
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> procs ---swap-- -----io---- ---system---- --------cpu--------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> r b si so bi bo ir cs us sy il wa st
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2 0 0 0 1 6 100 150 0 0 100 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 0 0 0 0 0 1750 3000 1 3 97 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 0 0 0 0 0 250 400 0 0 100 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 0 0 0 0 0 300 450 0 0 100 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 0 0 0 0 0 300 450 0 0 100 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Memory management ##########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># The End ####################################################&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Redirect output to a file.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-summary > server-summary.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="pt-mysql-summary">PT MySQL Summary&lt;/h2>
&lt;p>A Percona Toolkit utility that collects and displays a concise overview of a MySQL or Percona Server instance, including key configuration settings, performance metrics, storage engine details, replication status, buffer pool usage, and important global variables. It provides a fast, structured snapshot of the database environment, making it ideal for troubleshooting, tuning, and preparing information for support teams.&lt;/p>
&lt;h3 id="example-1">Example&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-mysql-summary
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Percona Toolkit MySQL Summary Report #######################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> System time | 2025-11-24 17:45:54 UTC (local TZ: EST -0500)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Instances ##################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Port Data Directory Nice OOM Socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ===== ========================== ==== === ======
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3306 /data0/mysql/data/ 0 0 /usr/local/mysql/mysql.sock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># MySQL Executable ###########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Path to executable | /usr/local/mysql/bin/mysqld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Has symbols | Yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Report On Port 3306 ########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> User | wayne@localhost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Time | 2025-11-24 12:45:54 (EST)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Hostname | pi16gb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Version | 8.4.6-6 Source distribution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Built On | Linux aarch64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Started | 2025-10-14 09:48 (up 41+02:57:35)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Databases | 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Datadir | /data0/mysql/data/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Processes | 2 connected, 2 running
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replication | Is not a replica, has 1 replicas connected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pidfile | /usr/local/mysql/mysqld.pid (exists)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Processlist ################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Command COUNT(*) Working SUM(Time) MAX(Time)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ------------------------------ -------- ------- --------- ---------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Binlog Dump GTID 1 1 3000000 3000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Query 1 1 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> User COUNT(*) Working SUM(Time) MAX(Time)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ------------------------------ -------- ------- --------- ---------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replication 1 1 3000000 3000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wayne 1 1 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Host COUNT(*) Working SUM(Time) MAX(Time)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ------------------------------ -------- ------- --------- ---------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 192.168.1.251 1 1 3000000 3000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> localhost 1 1 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db COUNT(*) Working SUM(Time) MAX(Time)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ------------------------------ -------- ------- --------- ---------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> NULL 2 2 3000000 3000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> State COUNT(*) Working SUM(Time) MAX(Time)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ------------------------------ -------- ------- --------- ---------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> init 1 1 0 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Source has sent all binlog to 1 1 3000000 3000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Status Counters (Wait 10 Seconds) ##########################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable Per day Per second 11 secs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Aborted_clients 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Binlog_snapshot_position 350000 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Binlog_cache_use 6000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Bytes_received 20000000 225 600
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Bytes_sent 2250000000 25000 4000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Table_open_cache_misses 400
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Table_open_cache_overflows 225
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Threads_created 9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Uptime 90000 1 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Table cache ################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Size | 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Usage | 100%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Key Percona Server features ################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Table &amp; Index Stats | Disabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Multiple I/O Threads | Enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Corruption Resilient | Enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Durable Replication | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Import InnoDB Tables | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Fast Server Restarts | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enhanced Logging | Disabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replica Perf Logging | Disabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Response Time Hist. | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Smooth Flushing | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> HandlerSocket NoSQL | Not Supported
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Fast Hash UDFs | Unknown
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Percona XtraDB Cluster #####################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Plugins ####################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> InnoDB compression | ACTIVE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Schema #####################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Specify --databases or --all-databases to dump and summarize schemas
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Noteworthy Technologies ####################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SSL | Yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Explicit LOCK TABLES | No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Delayed Insert | No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> XA Transactions | No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> NDB Cluster | No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Prepared Statements | Yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Prepared statement count | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># InnoDB #####################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Version | 8.4.6-6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Buffer Pool Size | 8.0G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Buffer Pool Fill | 30%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Buffer Pool Dirty | 0%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File Per Table | ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Page Size | 16k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Log File Size | 2 * 48.0M = 96.0M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Log Buffer Size | 64M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Flush Method | O_DIRECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Flush Log At Commit | 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> XA Support |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Checksums |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Doublewrite | ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> R/W I/O Threads | 4 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> I/O Capacity | 200
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Thread Concurrency | 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Concurrency Tickets | 5000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Commit Concurrency | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Txn Isolation Level | REPEATABLE-READ
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Adaptive Flushing | ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Adaptive Checkpoint |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Checkpoint Age | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> InnoDB Queue | 0 queries inside InnoDB, 0 queries in queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Oldest Transaction | 0 Seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> History List Len | 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Read Views | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Undo Log Entries | 0 transactions, 0 total undo, 0 max undo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pending I/O Reads | 0 buf pool reads, 0 normal AIO, 0 ibuf AIO, 0 preads
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pending I/O Writes | 0 buf pool (0 LRU, 0 flush list, 0 page); 0 AIO, 0 sync, 0 log IO (0 log, 0 chkp); 1 pwrites
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pending I/O Flushes | 0 buf pool, 0 log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Transaction States | 3xnot started
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># MyISAM #####################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Key Cache | 8.0M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pct Used | 20%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Unflushed | 0%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Security ###################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Users | 8 users, 0 anon, 0 w/o pw, 7 old pw
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Old Passwords |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Encryption #################################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No keyring plugins found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Binary Logging #############################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Binlogs | 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Zero-Sized | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Total Size | 437.9M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> binlog_format | ROW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expire_logs_days |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sync_binlog | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server_id | 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> binlog_do_db |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> binlog_ignore_db |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Noteworthy Variables #######################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Auto-Inc Incr/Offset | 1/1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> default_storage_engine | InnoDB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> flush_time | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> init_connect |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> init_file |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> join_buffer_size | 256k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sort_buffer_size | 256k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> read_buffer_size | 128k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> read_rnd_buffer_size | 256k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bulk_insert_buffer | 0.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max_heap_table_size | 16M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tmp_table_size | 16M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max_allowed_packet | 64M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> thread_stack | 1M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log_error | /var/log/mysql/mysqld.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log_warnings |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log_slow_queries |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log_queries_not_using_indexes | OFF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log_replica_updates | ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Configuration File #########################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Config File | /etc/my.cnf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[mysqld]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">character-set-server = utf8mb4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">authentication_policy = '*'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">port = 3306
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">socket = /usr/local/mysql/mysql.sock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pid-file = /usr/local/mysql/mysqld.pid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">basedir = /usr/local/mysql/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">datadir = /data0/mysql/data/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmpdir = /data0/mysql/tmp/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">general_log_file = /var/log/mysql/mysql-general.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log-error = /var/log/mysql/mysqld.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log_file = /var/log/mysql/slow_query.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_data_home_dir = /data0/mysql/data/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_group_home_dir = /data0/mysql/data/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_temp_data_file_path = ../tmp/ibtmp1:12M:autoextend:max:8G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_size = 8G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb-redo-log-capacity = 2G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_log_at_trx_commit = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_lock_wait_timeout = 50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_method = O_DIRECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_file_per_table = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_io_capacity = 200
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_instances = 8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_thread_concurrency = 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Memory management library ##################################
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">jemalloc is not enabled in mysql config for process with id 788
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># The End ####################################################&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Redirect output to file.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-mysql-summary > percona-server.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Capture both pt-summary and pt-mysql-summary into a single file.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-summary > percona-server-summary.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pt-mysql-summary >> percona-server-summary.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="pt-online-schema-change">PT Online Schema Change&lt;/h2>
&lt;p>A Percona Toolkit utility that performs online ALTER TABLE operations by creating a shadow copy of the table, applying the schema change to that copy, and keeping it in sync with the original using triggers until it is ready to swap. This workflow minimizes locking and reduces downtime, allowing large production tables to be altered safely with minimal impact on applications. However, it’s important to remind users that long-running queries or transactions holding metadata locks (MDL) on the table will still block the final swap, potentially delaying completion of the schema change.&lt;/p>
&lt;h3 id="examples">Examples&lt;/h3>
&lt;h4 id="adding-a-new-column">Adding a New Column&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "ADD COLUMN status TINYINT NOT NULL DEFAULT 0" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=mydb,t=orders \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This safely introduces a new column to a busy table without blocking reads or writes. The tool handles the copy, synchronization, and final table swap automatically.&lt;/p>
&lt;h4 id="modifying-a-column-type">Modifying a Column Type&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "MODIFY COLUMN price DECIMAL(10,2)" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=shop,t=products \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Changing column definitions—especially on large datasets—can be disruptive using standard SQL. With pt-osc, the migration happens online, keeping applications responsive throughout the operation.&lt;/p>
&lt;h4 id="dropping-an-unused-column">Dropping an Unused Column&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "DROP COLUMN old_flag" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=analytics,t=events \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Column drops can require a full table rebuild, making them great candidates for pt-osc. This example removes a legacy column while avoiding table locks.&lt;/p>
&lt;h4 id="adding-an-index">Adding an Index&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "ADD INDEX idx_user_id (user_id)" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=app,t=logins \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Index creation is another expensive operation for large tables. Here, pt-osc allows the index to be added online, improving performance without interrupting the application.&lt;/p>
&lt;h4 id="changing-a-primary-key">Changing a Primary Key&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "DROP PRIMARY KEY, ADD PRIMARY KEY(id, created_at)" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=orders,t=order_items \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Primary key modifications usually require a full table rewrite. pt-osc makes this process safer and easier on production systems by performing the change on a temporary shadow table.&lt;/p>
&lt;h4 id="performing-a-dry-run">Performing a Dry Run&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "ADD COLUMN test INT" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=mydb,t=mytable \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --dry-run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A dry run allows you to validate the plan and review the process without making any actual changes—a critical safeguard when preparing for production schema work.&lt;/p>
&lt;h4 id="printing-sql-changes-before-execution">Printing SQL Changes Before Execution&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-online-schema-change \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter "ADD COLUMN updated_at TIMESTAMP NULL" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> D=crm,t=customers \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --print \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Using –print provides transparency into the SQL operations the tool will perform. This is particularly useful during code reviews or change-control processes.&lt;/p>
&lt;h2 id="pt-show-grants">PT Show Grants&lt;/h2>
&lt;p>A Percona Toolkit utility that extracts MySQL user accounts and privileges and outputs them as clean, executable CREATE USER and GRANT statements. It normalizes and orders the privileges for readability, making it valuable for auditing security, documenting access, migrating users between servers, or preparing accurate privilege information for support and compliance purposes.&lt;/p>
&lt;h3 id="examples-1">Examples&lt;/h3>
&lt;h4 id="dump-all-grants-for-all-users">Dump All Grants for All Users&lt;/h4>
&lt;p>The simplest and most common use case is generating a complete privilege snapshot:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-show-grants&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This returns normalized CREATE USER and GRANT statements for every account in the instance. It’s ideal for audits, environment comparisons, and creating human-readable privilege reports.&lt;/p>
&lt;h4 id="show-grants-for-a-specific-user">Show Grants for a Specific User&lt;/h4>
&lt;p>If you want to inspect privileges for a single account, you can filter by user/host:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-show-grants --accounts='user@localhost'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This makes privilege debugging and user-level audits quick and targeted.&lt;/p>
&lt;h4 id="export-all-grants-to-a-file">Export All Grants to a File&lt;/h4>
&lt;p>To create a reusable backup of every user account:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-show-grants > grants.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The resulting file is a set of CREATE USER and GRANT statements that can be restored simply by executing:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql &lt; grants.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is an excellent practice before server upgrades, user cleanup, or major permission changes.&lt;/p>
&lt;h4 id="show-grants-for-multiple-accounts">Show Grants for Multiple Accounts&lt;/h4>
&lt;p>You can provide a comma-separated list of accounts to extract only what you need:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-show-grants --accounts='app@%,reporting@localhost,backup@localhost'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is ideal for teams that manage groups of service accounts across environments.&lt;/p>
&lt;h4 id="ignore-specific-system-accounts">Ignore Specific System Accounts&lt;/h4>
&lt;p>For cleanup scripts or custom inventory reports, skip built-in MySQL accounts:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-show-grants --ignore='mysql.sys@localhost,mysql.infoschema@localhost'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This focuses output on only the accounts relevant to your application.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>This post highlights the importance of using the right tool for the job—both in woodworking and in database engineering. For MySQL and Percona Server environments, the Percona Toolkit offers a set of powerful utilities that simplify diagnostics, troubleshooting, schema changes, and security audits.&lt;/p>
&lt;p>It introduces four key tools:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>pt-summary – Generates a high-level report of system hardware, OS settings, filesystems, networking, and performance metrics. Useful for support cases and quick environment overviews.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pt-mysql-summary – Produces a structured snapshot of a MySQL instance, including configuration, performance counters, replication status, storage engine details, and important variables. Ideal for tuning and issue analysis.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pt-online-schema-change – Enables online ALTER TABLE operations by copying and syncing the table in the background, minimizing downtime. Several examples show how to add, drop, or modify columns and indexes safely.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pt-show-grants – Extracts all MySQL users and privileges into clean, reproducible CREATE USER and GRANT statements. Helpful for audits, migrations, backups, and security reviews.&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Percona</category><category>toolkit</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>PXC</category><media:thumbnail url="https://percona.community/blog/2025/11/vintage-toolbox-open_hu_84f49383f15a7e1c.jpg"/><media:content url="https://percona.community/blog/2025/11/vintage-toolbox-open_hu_f74717849a7b76fb.jpg" medium="image"/></item><item><title>Percona Operator for MySQL Is Now GA, More MySQL Options for the Community on Kubernetes</title><link>https://percona.community/blog/2025/11/19/percona-operator-for-mysql-is-now-ga-more-mysql-options-for-the-community-on-kubernetes/</link><guid>https://percona.community/blog/2025/11/19/percona-operator-for-mysql-is-now-ga-more-mysql-options-for-the-community-on-kubernetes/</guid><pubDate>Wed, 19 Nov 2025 11:00:00 UTC</pubDate><description>We’re excited to share that the new Percona Operator for MySQL (based on Percona Server for MySQL) is officially in General Availability (GA)!</description><content:encoded>&lt;p>We’re excited to share that the new &lt;strong>&lt;a href="https://docs.percona.com/percona-operator-for-mysql/ps/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL (based on Percona Server for MySQL)&lt;/a>&lt;/strong> is officially in General Availability (GA)!&lt;/p>
&lt;p>This release introduces native &lt;strong>MySQL Group Replication&lt;/strong> support for &lt;strong>Kubernetes&lt;/strong>, providing our community with another open-source option for running reliable, consistent MySQL clusters at scale.&lt;/p>
&lt;p>This is about more choices for the community. Each MySQL replication technology addresses different real-world needs, and now you can choose the one that best fits your workloads.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="536" height="640" sizes="(max-width: 536px) 100vw, 536px" srcset="https://percona.community/blog/2025/11/introm_hu_a378a90cac4adf5d.webp 480w, https://percona.community/blog/2025/11/introm_hu_db44d0f2df34a48a.webp 536w" src="https://percona.community/blog/2025/11/introm_hu_db44d0f2df34a48a.webp" alt="MySQL Operator for MySQL Intro" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="what-this-means-for-the-community">What This Means for the Community&lt;/h2>
&lt;p>With this release, Percona now supports two &lt;strong>fully open-source MySQL Operators&lt;/strong>:&lt;/p>
&lt;h3 id="1-percona-operator-for-mysql-percona-server-for-mysql-new-and-ga">1. &lt;a href="https://docs.percona.com/percona-operator-for-mysql/ps/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL (Percona Server for MySQL)&lt;/a>, New and GA&lt;/h3>
&lt;ul>
&lt;li>Group Replication (synchronous)&lt;/li>
&lt;li>Asynchronous replication (Technical Preview)&lt;/li>
&lt;li>Native MySQL experience&lt;/li>
&lt;li>Auto-failover&lt;/li>
&lt;li>Kubernetes-native design&lt;/li>
&lt;/ul>
&lt;h3 id="2-percona-xtradb-cluster-operator-pxc">2. &lt;a href="https://docs.percona.com/percona-operator-for-mysql/pxc/index.html" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster Operator (PXC)&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>Galera-based synchronous replication&lt;/li>
&lt;li>Strong high availability&lt;/li>
&lt;li>Auto-failover&lt;/li>
&lt;li>Battle-tested for mission-critical workloads&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>These Operators complement each other; they are not replacements&lt;/strong>. They give users the freedom to choose the right replication model for their business and technical priorities.&lt;/p>
&lt;p>&lt;em>This GA release is a step in that direction, and we will continue publishing technical blog posts to explain when to use each Operator, how Group Replication works, and how this all fits into real-world Kubernetes environments&lt;/em>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1790" height="1118" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/11/two-operators_hu_fa7a5e50ea1d2b51.webp 768w, https://percona.community/blog/2025/11/two-operators_hu_b75a3fb1367e050d.webp 480w, https://percona.community/blog/2025/11/two-operators_hu_48f0037e268e5adb.webp 1400w" src="https://percona.community/blog/2025/11/two-operators_hu_48f0037e268e5adb.webp" alt="MySQL Operator for MySQL Intro Chart" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="call-for-community-testing-and-feedback">Call for Community Testing and Feedback&lt;/h2>
&lt;p>Asynchronous replication is now available in Technical Preview, we invite you to:&lt;/p>
&lt;ul>
&lt;li>Test it in your clusters&lt;/li>
&lt;li>Share your feedback&lt;/li>
&lt;li>Open GitHub issues&lt;/li>
&lt;li>Contribute docs or examples&lt;/li>
&lt;/ul>
&lt;p>Your feedback will guide the next features we bring to the Operator.&lt;/p>
&lt;h3 id="explore-percona-operator-for-mysql">Explore Percona Operator for MySQL:&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-mysql/ps/ReleaseNotes/Kubernetes-Operator-for-PS-RN1.0.0.html" target="_blank" rel="noopener noreferrer">Docs Percona Operator for MySQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mysql-operator" target="_blank" rel="noopener noreferrer">GitHub: Try it, test it, open issues, or contribute&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.linkedin.com/posts/percona_the-percona-cloud-native-team-is-happy-activity-7396585512536473600-bFZR/?utm_source=share&amp;utm_medium=member_ios&amp;rcm=ACoAAA_uTn0BQWSwnqQ-mUMcVZ7icaVGYa4mlVs" target="_blank" rel="noopener noreferrer">Announcement Percona Blog&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Edith Puclla</author><category>MySQL</category><category>Opensource</category><category>Cloud</category><category>Kubernetes</category><category>Operators</category><media:thumbnail url="https://percona.community/blog/2025/11/init_hu_93b48671fe9b894f.jpg"/><media:content url="https://percona.community/blog/2025/11/init_hu_9b71a12a7bfbff67.jpg" medium="image"/></item><item><title>OIDC in PostgreSQL: How It Works and Staying Secure</title><link>https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/</link><guid>https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/</guid><pubDate>Mon, 17 Nov 2025 09:00:00 UTC</pubDate><description>In the previous blog post about the topic, OAuth, OIDC and validators, we discussed basic terminologies to understand the differences between the protocols and how they relate to PostgreSQL.</description><content:encoded>&lt;p>In the previous blog post about the topic, &lt;a href="https://percona.community/blog/2025/11/07/oauth-oidc-validators/">OAuth, OIDC and validators&lt;/a>, we discussed basic terminologies to understand the differences between the protocols and how they relate to PostgreSQL.&lt;/p>
&lt;p>In this second part, we’ll go one step further and see how OIDC works exactly in other software and in PostgreSQL, and what OAuthBearer is about. We also focus on the possible attacks and dangers in this flow with some examples to showcase why it’s important to use a properly configured secure provider and to teach our users not to just skim through the authorization process.&lt;/p>
&lt;h3 id="can-you-keep-a-secret">Can you keep a secret?&lt;/h3>
&lt;p>Even the original OAuth RFC was designed to work in many different situations, and later extensions made it even more generic to support more use cases.
However, it also has to acknowledge that different setups have different requirements and limitations and, because of that, might require a different authorization flow.&lt;/p>
&lt;p>In one of our previous examples, we used applications such as CloudStorage or EditorApp.
We’ll continue to use them for a while for simplicity, but I want to emphasize that this example, even though it uses different names, is relevant and important for understanding how OAuth and OIDC work together with PostgreSQL.&lt;/p>
&lt;ul>
&lt;li>For CloudStorage, we can safely assume it’s a traditional backend-frontend application, since it has to store the data somewhere on a backend.&lt;/li>
&lt;li>For EditorApp, the answer can be different.
Since it doesn’t store photos directly but retrieves/saves them on CloudStorage, and also doesn’t store users directly but retrieves them from the Provider using OIDC, it has no requirement for a backend.
It is easily imaginable as a single page application, completely written in a modern JS framework, or even a downloadable version of it with Electron or a similar framework.&lt;/li>
&lt;/ul>
&lt;p>But why is this distinction important for us?
Because we actually have to authenticate and authorize multiple actors here, not just users:&lt;/p>
&lt;ul>
&lt;li>The applications, CloudStorage and EditorApp, have to trust the Provider to supply them with proper information about their users and their permissions.&lt;/li>
&lt;li>The OAuth Provider has to identify that it’s speaking to CloudStorage or EditorApp to tell them what exactly they can do with their users.&lt;/li>
&lt;/ul>
&lt;p>OAuth or OIDC providers don’t just register users – they also register client applications, and these applications receive either a randomly generated or user-specified identifier (“username”), depending on the Provider in question.
When we talk about authentication, that usually involves multiple credentials, such as a username and a password, not just a username.
The OAuth standard also recognizes that passwords are good practice and lets client applications use them. This is called a &lt;code>client secret&lt;/code>.&lt;/p>
&lt;p>But are all applications equal in this sense? Can all of them keep secrets?&lt;/p>
&lt;p>The answer to this question is, unfortunately, no.
In our example, CloudStorage has a backend – this means it can keep its secret on the backend, never sending it to the frontend.
This is how we usually treat our passwords, and this is what OAuth calls a &lt;strong>confidential client&lt;/strong>.&lt;/p>
&lt;p>EditorApp is, however, in a different situation.
It’s an application purely implemented in HTML and JavaScript, without any backend code.
Anybody can download its source, open it in a browser, and start using it.
It’s not capable of hiding its secret. Even if it has a client secret – which is optional in OAuth exactly because of this situation – a user with sufficient knowledge can extract this secret from its code.
And this is true even if it’s not a JavaScript application but rather a traditional desktop application written in a compiled language.
As long as it doesn’t have a backend, a part hidden from its users, there’s no way to completely hide the secret.
This is called a &lt;strong>public client&lt;/strong> in OAuth.&lt;/p>
&lt;h3 id="are-you-trying-to-log-in-to-application-x">Are you trying to log in to application “X”?&lt;/h3>
&lt;p>So how can the Provider make sure it’s talking to EditorApp and not to something else impersonating EditorApp?
Without a secret, without the client being confidential, it can never be sure about this.&lt;/p>
&lt;p>As long as we’re talking about web applications, which usually have URLs, it can try to minimize the chance of impersonation.
Providers can make sure that the redirects done during the authorization flow go to URLs configured by the client administrators / owners.
But even this can be circumvented, and in the case of desktop applications, it’s not usable.&lt;/p>
&lt;p>This is why the Provider uses a different strategy.
Since it can’t validate for itself that the thing claiming to be EditorApp is really EditorApp or something else, it informs the user.
When the user clicks on the “Login with &lt;X>” button, instead of silently authorizing the login, it first displays an information screen to the user.
This is called the &lt;code>consent screen&lt;/code>, and it has to display two things:&lt;/p>
&lt;ul>
&lt;li>That user &lt;Bob> is trying to log in to application &lt;EditorApp>&lt;/li>
&lt;li>That &lt;EditorApp> is requesting permissions to read and write &lt;CloudStorage>&lt;/li>
&lt;/ul>
&lt;p>Along with this information, it has to ask the question:
Is the above information correct? Is &lt;Bob> really trying to log in to &lt;EditorApp>? Is &lt;Bob> okay with granting these permissions to &lt;CloudStorage>?&lt;/p>
&lt;p>And only if the user, &lt;Bob> agrees can the request proceed.&lt;/p>
&lt;h3 id="practical-limitations">Practical limitations&lt;/h3>
&lt;p>The above all sounds very nice and secure, but unfortunately we have to discuss some problems with it in practice.&lt;/p>
&lt;p>Most importantly, users have a tendency to click “next, next, next” without properly reading, especially if they’re completing the same login flow for the 100th time.
Even if a Provider implements the RFCs perfectly in the most secure way possible, it doesn’t help if the user just wants to get through the process quickly and doesn’t check the details about the application name and permissions properly.&lt;/p>
&lt;p>Another issue is that providers don’t always properly follow the RFCs.
For some providers, it’s configurable – administrators can choose when the consent screen is displayed.
They can choose to only display it once when the user first logs into an application, to display it every time the user logs in to the application, or to never display it.
Some providers allow these configuration settings for the permissions, for the application name, or both.
Some providers simply never display permissions and always just say something like “be sure to trust this application &lt;EditorApp>”.&lt;/p>
&lt;p>Unfortunately, during our testing we found providers that in some cases completely skipped the consent screen, even when we tried to explicitly configure that it’s always required. Be skeptical about the authorization flow used by the Provider, verify that it’s secure enough, and don’t hesitate to change providers or report bug reports to the developers if something isn’t as it should be!&lt;/p>
&lt;h3 id="limited-devices">Limited devices&lt;/h3>
&lt;p>Everything we discussed above is nice, but PostgreSQL isn’t a website.
Something using it might be one, and in that case, everything we discussed above applies – but to go back to the only currently supported client, &lt;code>psql&lt;/code>, that’s a console application and can’t display a graphical web browser for login.&lt;/p>
&lt;p>OAuth also thought about similar clients – not exactly console applications, but primarily devices that can’t display a browser and process a normal login flow to the Provider.
These are called &lt;strong>limited devices&lt;/strong> because the primary goal of this extension, &lt;a href="https://www.rfc-editor.org/rfc/rfc8628.html" target="_blank" rel="noopener noreferrer">RFC 8628&lt;/a>, was to support specialized hardware where the user either can’t or doesn’t want to log in.&lt;/p>
&lt;p>A typical example is a smart TV.
While it can display a browser and a virtual keyboard, I wouldn’t want to type my password and log in to a TV using that.
But these devices can be even more limited. The only requirement for them is that they should be able to display a verification code and instruct the user where (URL) to enter that verification code on another device, which is capable and secure enough to handle the normal password login process or where the user is already logged in.&lt;/p>
&lt;p>Other than this indirection, the login process is similar.
The user sees the code, opens the device login webpage on another device, enters the code, and then receives a similar consent screen as before.
This consent screen states that device/application &lt;X> is trying to log in and requests permissions &lt;Something>.
The user then clicks the approve button, while in the background the device periodically checks for approval.
Once approved, the authorization proceeds as normal on the limited device.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant Device as Limited Device&lt;br/>(psql)
participant Provider as OIDC Provider
participant Browser as User's Browser&lt;br/>(phone/laptop)
Device->>Provider: Request device code
Provider->>Device: Device code: ABC123&lt;br/>URL: provider.com/device
Note over Device: Display code and URL&lt;br/>to user
Browser->>Provider: Navigate to provider.com/device
Provider->>Browser: Show code entry form
Browser->>Provider: Enter code: ABC123
Note over Provider: Verify code is valid
Provider->>Browser: Show consent screen&lt;br/>"psql is requesting access"
Browser->>Provider: User clicks "Approve"
loop Polling every few seconds
Device->>Provider: Is code ABC123 approved?
Provider->>Device: Not yet...
end
Device->>Provider: Is code ABC123 approved?
Provider->>Device: Yes! Here's your access token
Note over Device: Token received,&lt;br/>authentication complete
&lt;/pre>
&lt;p>While &lt;code>psql&lt;/code> isn’t strictly a limited device, it’s in a similar situation.
It’s possible that somebody is using it directly on a computer with a UI, with a web browser already logged in to the OIDC provider, but this is an unlikely scenario.
More likely, the user has an SSH session open to another computer, running &lt;code>psql&lt;/code> in it.
Even if that remote computer has a graphical user interface and browser installed, displaying a login page on it wouldn’t help – the user wouldn’t see it.
But it’s much more likely that it’s a console-only virtual machine / container somewhere, without a proper way to do a graphical login.
In this sense, &lt;code>psql&lt;/code> together with its environment is a limited device.&lt;/p>
&lt;h3 id="can-we-mix-login-processes">Can we mix login processes?&lt;/h3>
&lt;p>Before we talk about vulnerabilities and staying secure, it’s important to clarify something.
OAuth/OIDC supports many different authentication workflows, including but not limited to the possibilities listed above.
Support for specific flows also varies between vendors – the supported features and nuances vary from Provider to Provider.&lt;/p>
&lt;p>But if a vendor supports a specific flow, that doesn’t mean it’s automatically usable by all clients.
Every provider we tested so far lets the administrators configure which authorization flows they want to support.
If a specific application only works with confidential clients, the best configuration is to disable anything else.&lt;/p>
&lt;p>The PostgreSQL wire protocol (and server) doesn’t enforce the use of any specific flow, but a generic validator plugin also can’t assume that the server uses some specific configuration.
This isn’t something the validator is capable of checking, as it gets executed on the server, after the authorization flow already concluded on the client side.&lt;/p>
&lt;p>Strictly speaking, the server doesn’t even know the ID of the client (application), only the issuer URL.&lt;/p>
&lt;p>And if anyone wants to be able to use the &lt;code>psql&lt;/code> command, that only supports the limited device flow.&lt;/p>
&lt;h3 id="oauthbearer">OAuthBearer&lt;/h3>
&lt;p>I already mentioned this in the previous blog post, and a few times in this one.
It’s important to understand that the PostgreSQL project has multiple different roles in these authorization flows.
The client and the server/validator are two different actors, and the client can be any client, not just &lt;code>libpq&lt;/code>.
It can be either a well-known third-party implementation of the wire protocol or a completely custom implementation created by an attacker with malicious intent.&lt;/p>
&lt;p>From a security standpoint, the server/validator can’t trust the client or assume anything about the authentication process done by the client.&lt;/p>
&lt;p>This design isn’t unique to PostgreSQL.
Support for OAuth over non-HTTP protocols, using the SASL mechanism (which was already supported by PostgreSQL previously), is called OAuthBearer, &lt;a href="https://www.rfc-editor.org/rfc/rfc7628.html" target="_blank" rel="noopener noreferrer">RFC 7628&lt;/a>, and it’s implemented by PostgreSQL similarly to how other software does it.&lt;/p>
&lt;p>The idea of OAuthBearer is that the client using the non-HTTP service (in our case, PostgreSQL) creates an &lt;code>access token&lt;/code> in some way.
Then this client can use this access token to connect to PostgreSQL and possibly other services.
Even if the client uses multiple services that all authenticate with OIDC, it only has to complete the OAuth flow once.&lt;/p>
&lt;p>On the other side, the server doesn’t have to do anything else with OAuth other than validating that the token it received is correct and valid.
It doesn’t have to deal with multiple flows. On the other hand, it can’t assume anything about the flow.&lt;/p>
&lt;h3 id="can-you-please-enter-the-code">Can you please enter the code?&lt;/h3>
&lt;p>Why are the above details important, and why did I repeat the same description multiple times, worded slightly differently?&lt;/p>
&lt;p>Because OIDC with public clients, even if implemented properly without mistakes, is vulnerable to the human factor.
And in practice, most of the time, PostgreSQL and OIDC means using public clients.&lt;/p>
&lt;p>There are multiple possible attack vectors that were previously used, and are still being used, to gain access to services, and there’s no way to fully secure against them.
Both are enabled by the fact that with OAuthBearer, we have no control over the authentication process – we have to trust that the access token sent to us by the client was indeed created by the user with the intent to log in to this server.&lt;/p>
&lt;p>One very simple “attack” against the device authentication flow is to ask the user nicely.
Since the two parts of the process – requesting and using the &lt;code>device code&lt;/code>, and verifying the device code – can happen at two different locations in a completely valid setup, there’s not much any software can do programmatically.&lt;/p>
&lt;p>In this scenario, the attacker sends an email or calls the victim and comes up with some reason why the user has to go immediately to the device login page and enter the code.
The reason is, of course, usually something completely unrelated, like “this is the code to join the meeting,” or “go to the website and enter this code to verify your account,” or anything similar.
This is called &lt;strong>device code phishing&lt;/strong>, and it’s made worse by the fact that some providers don’t display detailed enough consent screens during device code authentication, placing even careful users in vulnerable situations.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant Attacker
participant Provider as OIDC Provider
participant Victim as Victim's Browser
participant PG as PostgreSQL Database
Attacker->>Provider: Request device code
Provider->>Attacker: Code: XYZ789&lt;br/>URL: provider.com/device
Note over Attacker: Attacker now has&lt;br/>device code
Attacker->>Victim: Email: "Enter code XYZ789&lt;br/>at provider.com/device&lt;br/>to verify your account"
Victim->>Provider: Navigate to provider.com/device
Provider->>Victim: Enter device code
Victim->>Provider: Enter code: XYZ789
Note over Victim: User thinks they're&lt;br/>verifying their account
Provider->>Victim: Show consent screen&lt;br/>"psql requesting database access"
Victim->>Provider: Click "Approve"&lt;br/>(without reading carefully)
Provider->>Attacker: Access token granted!
Note over Attacker,PG: Attacker now has valid token
Attacker->>PG: Connect with stolen token
PG->>Attacker: Connection successful
Note over Attacker,PG: Attacker has full&lt;br/>database access
&lt;/pre>
&lt;p>Unfortunately, this can’t be prevented as long as device code flow is enabled.
It can be mitigated to some degree by educating users and making sure that the consent screen is always displayed and is very clear about the request details.&lt;/p>
&lt;h3 id="client-id-spoofing">Client ID spoofing&lt;/h3>
&lt;p>Another easy OAuth/OIDC attack vector relies on the fact that we’re using a public client – meaning that either there isn’t a client secret, or even if there is one, it’s not really a secret.
It’s easy to create a legitimate-looking website that uses OIDC for something else and starts an authorization flow…
but in the background, it uses the same client credentials as the PostgreSQL client.&lt;/p>
&lt;p>This again relies on either a non-securely configured consent screen or a careless user who doesn’t read the details about the request.
And compared to the previous example, this doesn’t require the device flow – it can work with other flows too.
In the world of LLMs, it’s very easy to create a valid-looking website tailor-made just for this purpose.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant Victim as User
participant Fake as Fake Website&lt;br/>"Photo Gallery"
participant Provider as OIDC Provider
participant PG as PostgreSQL Database
Victim->>Fake: Visit fake-photo-gallery.com
Fake->>Victim: "Login with your account"
Note over Fake: Uses REAL psql client ID!&lt;br/>(public, can't be hidden)
Victim->>Fake: Click "Login"
Fake->>Provider: OAuth request with&lt;br/>psql client ID
Provider->>Victim: Show consent screen&lt;br/>"psql requesting access"
Note over Victim: User thinks:&lt;br/>"I'm logging into&lt;br/>Photo Gallery"
Victim->>Provider: Click "Approve"
Provider->>Fake: Access token
Note over Fake: Fake site now has&lt;br/>database token!
Fake->>PG: Connect with token
PG->>Fake: Connection successful
Note over Fake,PG: Attacker has database access
&lt;/pre>
&lt;p>Similarly to the previous example, there’s not much we can do to prevent this in the plugin.
OAuth has extensions that aim to make the process more secure, such as &lt;a href="https://www.rfc-editor.org/rfc/rfc7636.html" target="_blank" rel="noopener noreferrer">PKCE (RFC 7636)&lt;/a> or &lt;a href="https://www.rfc-editor.org/rfc/rfc9449.html" target="_blank" rel="noopener noreferrer">DPoP (RFC 9449)&lt;/a>, preventing specific situations, but none of those help with OAuthBearer.
As the entire authorization process happens on the client side, the validator on the server can’t do anything but assume that if the access token is valid, then the user created it intentionally.&lt;/p>
&lt;h3 id="educate-your-users">Educate your users!&lt;/h3>
&lt;p>Once more I’d like to emphasize that while we can’t prevent these attack vectors completely, it is possible to minimize the risk by teaching, both your users and administrators.&lt;/p>
&lt;p>Teach your users to:&lt;/p>
&lt;ul>
&lt;li>Never enter device codes unless you initiated the login process yourself&lt;/li>
&lt;li>Always read the consent screen carefully, even if you’ve seen it before&lt;/li>
&lt;li>Verify the application name matches what you’re trying to access&lt;/li>
&lt;li>Be suspicious of unexpected emails or messages asking you to enter codes&lt;/li>
&lt;li>When in doubt, don’t approve – contact your administrator instead&lt;/li>
&lt;/ul>
&lt;p>And your administrators to:&lt;/p>
&lt;ul>
&lt;li>Understand that OIDC Provider selection and configuration isn’t just a checkbox item&lt;/li>
&lt;li>If a provider can’t be configured in a secure way, it presents a security vulnerability to any client using it&lt;/li>
&lt;li>Always enable and require consent screens for public client flows, for every login, not just for registration&lt;/li>
&lt;li>Make sure the consent screen clearly displays the application name and requested permissions&lt;/li>
&lt;/ul>
&lt;p>These attacks aren’t theoretical – they’re actively used in the wild.
The combination of OAuthBearer’s trust model and the inherent limitations of public clients means that proper provider configuration and user awareness are your primary defenses.&lt;/p>
&lt;h3 id="next-steps">Next steps&lt;/h3>
&lt;p>Now that we understand how OIDC works with PostgreSQL and the security considerations involved, it’s time to see this in practice.&lt;/p>
&lt;p>In the next blog post, we’ll walk through setting up Keycloak with PostgreSQL from scratch.
We’ll cover both getting Keycloak running and configuring it securely for PostgreSQL authentication, even if you’ve never worked with Keycloak before.
You’ll see exactly how to configure the settings we discussed here and how to test that everything works correctly.&lt;/p>
&lt;p>Stay tuned for practical, hands-on setup instructions!&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>OIDC</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2025/11/oidc2_hu_676fa04555556171.jpg"/><media:content url="https://percona.community/blog/2025/11/oidc2_hu_965aca32f15aec8b.jpg" medium="image"/></item><item><title>PGScorecard - PostgreSQL Compatibility Index</title><link>https://percona.community/blog/2025/11/13/pgscorecard-postgresql-compatibility-index/</link><guid>https://percona.community/blog/2025/11/13/pgscorecard-postgresql-compatibility-index/</guid><pubDate>Thu, 13 Nov 2025 00:00:00 UTC</pubDate><description>We’re excited to share that our recent test run using the Postgres Compatibility Index (PCI) achieved 100% compatibility.</description><content:encoded>&lt;p>We’re excited to share that our recent test run using the &lt;a href="https://github.com/secp256k1-sha256/postgres-compatibility-index/blob/main/readme.md" target="_blank" rel="noopener noreferrer">Postgres Compatibility Index (PCI)&lt;/a> achieved 100% compatibility.&lt;/p>
&lt;p>The PCI was created to bring clarity to the often used but loosely defined term “PostgreSQL compatible.” As Mayur explains in his article &lt;a href="https://drunkdba.medium.com/the-making-of-postgres-is-5034c0dc4639" target="_blank" rel="noopener noreferrer">The Making of ‘Postgres Is’&lt;/a>, the goal is simple: to ensure that when a system claims to be compatible with PostgreSQL, it truly behaves like upstream PostgreSQL in practice. The PCI accomplishes this by running a comprehensive set of tests across features like data types, procedural functions, constraints, extensions, and more, and producing a measurable, transparent score. This gives users and vendors a reliable benchmark rather than relying on marketing claims.&lt;/p>
&lt;p>Compatibility matters because many organizations rely on PostgreSQL variants, repackaged distributions, or vendor supported systems. They want the confidence that their schemas, tools, extensions, ORMs, client libraries, and workflows will continue to work as expected. A system that drifts from upstream PostgreSQL can introduce subtle risks such as, unsupported features, migration challenges, and vendor lock-in.&lt;/p>
&lt;p>Achieving a perfect PCI score means our system supports the full baseline feature set as currently defined. It demonstrates that users can rely on the same behavior as community PostgreSQL, whether they are self-hosting, using a vendor-supported version, or integrating with existing tools. Importantly, it also shows that you can have a fully open-source system while still benefiting from vendor support, without compromising compatibility.&lt;/p>
&lt;p>In a world where “PostgreSQL compatible” is often a vague claim, initiatives like the PCI provide the needed help with transparency and comparability. It helps to protect you from marketing claims in the PostgreSQL ecosystem, ensuring that tooling, and workflows continue to function reliably.&lt;/p>
&lt;p>Special thanks to Mayur, whose initiative is helping define a clear, standardized framework for PostgreSQL compatibility.&lt;/p>
&lt;blockquote>
&lt;p>The test run was done against &lt;a href="https://docs.percona.com/postgresql/17/index.html" target="_blank" rel="noopener noreferrer">Percona Server for PostgreSQL 17.6.1&lt;/a>. &lt;a href="https://github.com/secp256k1-sha256/postgres-compatibility-index/blob/main/postgres-compatibility-index/outputs/Percona.json" target="_blank" rel="noopener noreferrer">Click for test result&lt;/a>. Percona Server for PostgreSQL is a binary-compatible, open source drop-in replacement for PostgreSQL with the currently needed enhancements, to make &lt;a href="https://docs.percona.com/pg-tde/index.html" target="_blank" rel="noopener noreferrer">Transparent Data Encryption (TDE)&lt;/a> work.&lt;/p>&lt;/blockquote></content:encoded><author>Kai Wagner</author><category>Percona</category><category>pg_tde</category><category>pg_kwagner</category><category>PostgreSQL</category><category>Compliance</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2025/11/pci-pgscorecard_hu_72fafee18d4d001f.jpg"/><media:content url="https://percona.community/blog/2025/11/pci-pgscorecard_hu_2976e8260d632fb8.jpg" medium="image"/></item><item><title>MySQL Memory Usage: A Guide to Optimization</title><link>https://percona.community/blog/2025/11/11/mysql-memory-usage-a-guide-to-optimization/</link><guid>https://percona.community/blog/2025/11/11/mysql-memory-usage-a-guide-to-optimization/</guid><pubDate>Tue, 11 Nov 2025 00:00:00 UTC</pubDate><description>Struggling with MySQL memory spikes? Knowing how and where memory is allocated can make all the difference in maintaining a fast, reliable database. From global buffers to session-specific allocations, understanding the details of MySQL’s memory management can help you optimize performance and avoid slowdowns. Let’s explore the core elements of MySQL memory usage with best practices for trimming excess in demanding environments.</description><content:encoded>&lt;p>Struggling with MySQL memory spikes? Knowing how and where memory is allocated can make all the difference in maintaining a fast, reliable database. From global buffers to session-specific allocations, understanding the details of MySQL’s memory management can help you optimize performance and avoid slowdowns. Let’s explore the core elements of MySQL memory usage with best practices for trimming excess in demanding environments.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1680" height="593" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_d6215f52fe3f5ab5.webp 768w, https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_be83e83e0a0f27b2.webp 480w, https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_ee0b33309465bcba.webp 1400w" src="https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_ee0b33309465bcba.webp" alt="Releem Dashboard - RAM usage" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="how-mysql-uses-memory">How MySQL Uses Memory&lt;/h2>
&lt;p>MySQL dynamically manages memory across several areas to process queries, handle connections, and optimize performance. The two primary areas of memory usage include:&lt;/p>
&lt;h3 id="global-buffers">Global Buffers&lt;/h3>
&lt;p>These are shared by the entire MySQL server and include components like the InnoDB buffer pool, key buffer, and query cache. The InnoDB buffer pool is particularly memory-intensive, especially in data-heavy applications, as it stores frequently accessed data and indexes to speed up queries.&lt;/p>
&lt;h3 id="connection-per-thread-buffers">Connection (per thread) Buffers&lt;/h3>
&lt;p>When a client connects, MySQL allocates memory specifically for that session. This includes sort buffers, join buffers, and temporary table memory. The more concurrent connections you have, the more memory is consumed. Session buffers are critical to monitor in high-traffic environments.&lt;/p>
&lt;h2 id="why-mysql-memory-usage-might-surge">Why MySQL Memory Usage Might Surge&lt;/h2>
&lt;p>Memory spikes in MySQL often result from specific scenarios or misconfigurations. Here are a few examples:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>High Traffic with Large Connection Buffers&lt;/strong>: A surge in concurrent connections can quickly exhaust memory if sort or join buffers are set too large.&lt;/li>
&lt;li>&lt;strong>Complex Queries&lt;/strong>: Queries with large joins, subqueries, or extensive temporary table usage can temporarily allocate significant memory, especially when poorly optimized.&lt;/li>
&lt;li>&lt;strong>Oversized InnoDB Buffer Pool&lt;/strong> : Setting the &lt;a href="https://releem.com/docs/mysql-performance-tuning/innodb_buffer_pool_size" target="_blank" rel="noopener noreferrer">InnoDB buffer pool size&lt;/a> too large for the server’s available memory can trigger swapping, severely degrading database and server performance.&lt;/li>
&lt;li>&lt;strong>Large Temporary Tables&lt;/strong> : When temporary tables exceed the in-memory limit ( &lt;a href="https://releem.com/docs/mysql-performance-tuning/tmp_table_size" target="_blank" rel="noopener noreferrer">tmp_table_size&lt;/a> ), they are written to disk, consuming additional resources and slowing down operations.&lt;/li>
&lt;li>&lt;strong>Inefficient Indexing&lt;/strong> : A lack of proper indexes forces MySQL to perform full table scans, increasing memory and CPU usage for even moderately complex queries.&lt;/li>
&lt;/ul>
&lt;h2 id="best-practices-for-controlling-mysql-memory-usage">Best Practices for Controlling MySQL Memory Usage&lt;/h2>
&lt;p>When you notice MySQL using more memory than expected, consider the following strategies:&lt;/p>
&lt;h3 id="1-set-limits-on-global-buffers">1. Set Limits on Global Buffers&lt;/h3>
&lt;ul>
&lt;li>Configure &lt;a href="https://releem.com/docs/mysql-performance-tuning/innodb_buffer_pool_size" target="_blank" rel="noopener noreferrer">innodb_buffer_pool_size&lt;/a> to 60-70% of available memory for InnoDB-heavy workloads. For smaller workloads, scale it down to avoid overcommitting memory.&lt;/li>
&lt;li>Keep &lt;a href="https://releem.com/docs/mysql-performance-tuning/innodb_log_buffer_size" target="_blank" rel="noopener noreferrer">innodb_log_buffer_size&lt;/a> at a practical level (e.g., 16MB) unless write-heavy workloads demand more.&lt;/li>
&lt;li>Adjust &lt;a href="https://releem.com/docs/mysql-performance-tuning/key_buffer_size" target="_blank" rel="noopener noreferrer">key_buffer_size&lt;/a> for MyISAM tables, ensuring it remains proportionate to table usage to avoid unnecessary memory allocation.&lt;/li>
&lt;/ul>
&lt;h3 id="2-adjust-connection-buffer-sizes">2. Adjust Connection Buffer Sizes&lt;/h3>
&lt;ul>
&lt;li>Reduce &lt;a href="https://releem.com/docs/mysql-performance-tuning/sort_buffer_size" target="_blank" rel="noopener noreferrer">sort_buffer_size&lt;/a> and &lt;a href="https://releem.com/docs/mysql-performance-tuning/join_buffer_size" target="_blank" rel="noopener noreferrer">join_buffer_size&lt;/a> to balance memory usage with query performance, especially in environments with high concurrency.&lt;/li>
&lt;li>Optimize &lt;a href="https://releem.com/docs/mysql-performance-tuning/tmp_table_size" target="_blank" rel="noopener noreferrer">tmp_table_size&lt;/a> and &lt;a href="https://releem.com/docs/mysql-performance-tuning/max_heap_table_size" target="_blank" rel="noopener noreferrer">max_heap_table_size&lt;/a> to control in-memory temporary table allocation and avoid excessive disk usage.&lt;/li>
&lt;/ul>
&lt;h3 id="3-fine-tune-table-caches">3. Fine-Tune Table Caches&lt;/h3>
&lt;ul>
&lt;li>Adjust &lt;a href="https://releem.com/docs/mysql-performance-tuning/table_open_cache" target="_blank" rel="noopener noreferrer">table_open_cache&lt;/a> to avoid bottlenecks while considering OS file descriptor limits.&lt;/li>
&lt;li>Configure &lt;a href="https://releem.com/docs/mysql-performance-tuning/table_definition_cache" target="_blank" rel="noopener noreferrer">table_definition_cache&lt;/a> to manage table metadata efficiently, especially in environments with many tables or foreign key relationships.&lt;/li>
&lt;/ul>
&lt;h3 id="4-control-thread-cache-and-connection-limits">4. Control Thread Cache and Connection Limits&lt;/h3>
&lt;ul>
&lt;li>Use &lt;a href="https://releem.com/docs/mysql-performance-tuning/thread_cache_size" target="_blank" rel="noopener noreferrer">thread_cache_size&lt;/a> to reuse threads effectively and reduce overhead from frequent thread creation.&lt;/li>
&lt;li>Adjust &lt;a href="https://releem.com/docs/mysql-performance-tuning/thread_stack" target="_blank" rel="noopener noreferrer">thread_stack&lt;/a> and &lt;strong>net_buffer_length&lt;/strong> to suit your workload while keeping memory usage scalable.&lt;/li>
&lt;li>Limit &lt;a href="https://releem.com/docs/mysql-performance-tuning/max_connections" target="_blank" rel="noopener noreferrer">max_connections&lt;/a> to a level appropriate for your workload, preventing excessive session buffers from overwhelming server memory.&lt;/li>
&lt;/ul>
&lt;h3 id="5-track-temporary-table-usage">5. Track Temporary Table Usage&lt;/h3>
&lt;p>Monitor temporary table usage and reduce memory pressure by optimizing queries that rely on GROUP BY, ORDER BY, or UNION.&lt;/p>
&lt;h3 id="6-use-mysql-memory-calculator">6. Use MySQL Memory Calculator&lt;/h3>
&lt;p>Incorporate tools like the &lt;a href="https://releem.com/tools/mysql-memory-calculator" target="_blank" rel="noopener noreferrer">MySQL Memory Calculator by Releem&lt;/a> to estimate memory usage. Input your MySQL configuration values, and the calculator will provide real-time insights into maximum memory usage. This prevents overcommitting your server’s memory and helps allocate resources effectively.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1680" height="1668" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_15299358ddf7f1c6.webp 768w, https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_bcf5196dbcdc343b.webp 480w, https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_98125a1e303e79e6.webp 1400w" src="https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_98125a1e303e79e6.webp" alt="MySQL Memory Calculator" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="7-monitor-query-performance">7. Monitor Query Performance&lt;/h3>
&lt;p>High-memory-consuming queries, such as those with large joins or sorts, queries without indexes, can affect memory usage. Use &lt;a href="https://releem.com/query-analytics" target="_blank" rel="noopener noreferrer">Releem’s Query Analytics and Optimization feature&lt;/a> to determine inefficient queries and gain insights on further tuning opportunities.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2582" height="974" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_97224b204b9faf9c.webp 768w, https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_6d989a096e54f1ea.webp 480w, https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_6ab9fbc34f4c895a.webp 1400w" src="https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_6ab9fbc34f4c895a.webp" alt="Releem Dashboard - Query Analytics" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="simplifying-mysql-memory-tuning-with-releem">Simplifying MySQL Memory Tuning with Releem&lt;/h2>
&lt;p>Releem takes the guesswork out of MySQL optimization by automatically analyzing your setup and suggesting configuration changes that align with your memory limits and performance needs. Whether you’re dealing with complex workloads or simply don’t have time for manual adjustments, Releem makes it easier to keep MySQL running smoothly.&lt;/p></content:encoded><author>Roman Agabekov</author><category>MySQL</category><category>MariaDB</category><category>Percona</category><category>DBA Tools</category><media:thumbnail url="https://percona.community/blog/2025/11/mysql_memory_usage_badge_hu_cc1a4044f70f1723.jpg"/><media:content url="https://percona.community/blog/2025/11/mysql_memory_usage_badge_hu_a3243c5f91ca16a8.jpg" medium="image"/></item><item><title>A thread through my 2025 Postgres events</title><link>https://percona.community/blog/2025/11/10/thread-through-2025-pgconfs/</link><guid>https://percona.community/blog/2025/11/10/thread-through-2025-pgconfs/</guid><pubDate>Mon, 10 Nov 2025 07:00:00 UTC</pubDate><description>I recently got back from PostgreSQL Conference Europe in Riga, marking the end of my conference activities for 2025. The speakers were great. The audience, for the Extensions Showcase on Community Day on Tuesday and my Kubernetes from the database out talk, were great. The event team was great. The singing at karaoke was terrible, but it’s supposed to be.</description><content:encoded>&lt;p>I recently got back from PostgreSQL Conference Europe in Riga, marking the end of my conference activities for 2025. The speakers were great. The audience, for the Extensions Showcase on Community Day on Tuesday and my Kubernetes from the database out talk, were great. The event team was great. The singing at karaoke was terrible, but it’s supposed to be.&lt;/p>
&lt;p>After attending a good few events this year, starting with CERN PGDay in mid-January, I wanted to write something about more than just the most recent event. I see a common thread across presentations and sessions at a number of events over the year, that is, scale-out Postgres and particularly, its use in non-profit scientific environments.&lt;/p>
&lt;h3 id="the-beginning-and-end-users">The (beginning and) end users&lt;/h3>
&lt;p>Far fewer data processing challenges require pooling the resources of many physical servers these days, with servers getting bigger and storage faster. Scientific data analysis and managing large, complex scientific facilities still do. I saw three presentations on this: Rafal Kulaga, Antonin Kveton and Martin Zemko’s on &lt;a href="https://indico.cern.ch/event/1471762/contributions/6280212/" target="_blank" rel="noopener noreferrer">managing CERN’s SCADA data&lt;/a>; Daniel Krefl and Krzysztof Nienartowicz at CERN on &lt;a href="https://indico.cern.ch/event/1471762/contributions/6280216/" target="_blank" rel="noopener noreferrer">how Sendai queries variable star data&lt;/a>; and Jaoquim Oliveira in Riga on &lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7138-from-stars-to-storage-engines-migrating-big-science-workloads-beyond-greenplum/" target="_blank" rel="noopener noreferrer">managing the European Space Agency’s (ESA’s) survey mission data&lt;/a>.&lt;/p>
&lt;p>I admit a fondness for ESA’s GAIA catalog dataset. After I was lucky enough to do a proof of concept project on joining it with other catalog data, it has provided significant intellectual interest. Don’t let me get started on the possible ways to optimise computationally expensive inequality joins on horribly skewed data, unless you really care about the problem. My interest in a dataset discussed in two of these talks is not why the thread connecting them is worth commenting on. All three presentations had a lot of content on selecting or developing database technologies for the work they were doing. That’s worth discussing a bit further.&lt;/p>
&lt;h3 id="getting-the-details-right">Getting the details right&lt;/h3>
&lt;p>The thread of sharded, scale out, or Massively Parallel Processing (MPP) Postgres connects end user stories at my first event of the year and my last, along with stories of building this software at events in between. At PGConf.dev in Montreal David Wein gave a very condensed explanation of how AWS’s Aurora Limitless handles distributed snapshot isolation (&lt;a href="https://www.youtube.com/watch?v=UrRkHSxP2xE&amp;t=378s" target="_blank" rel="noopener noreferrer">watch the lightning talk at on YouTube&lt;/a>), there was also an unconference session on handling the issue in core Postgres the next day. For an in-depth explanation of of what the distributed snapshot problem is and how it may be addressed, see &lt;a href="https://www.postgresql.eu/events/pgconfeu2024/schedule/session/5710-high-concurrency-distributed-snapshots/" target="_blank" rel="noopener noreferrer">Ants Aasma’s talk from PGConf.EU 2024&lt;/a>&lt;/p>
&lt;p>The organisations with the data are looking for open source software solutions and bumping into issues around open core licensing, project contribution breadth, project activity levels, project governance. The Postgres developer community is working on the knottiest of the problems in this space, trying to get it absolutely right. In the mean-time, various forks and extensions are delivering useful functionality for the owners of these big, complex datasets.
Useful, but could do better&lt;/p>
&lt;p>If this were working out for everyone, there wouldn’t be a story to tell. Sednai are building Potgres-XZ, which builds on TBase, which built on Postgres-XL. The ESAC Science Data Centre (ESDC) is facing a decision between two single-vendor projects, where one vendor doesn’t provide support for on-premises deployments. CERN procurement sought written assurances over license terms for TimescaleDB, since the CERN facilities organisation may be viewed as a service provider to their hosted scientific projects.&lt;/p>
&lt;p>This pattern of licenses built specifically to avoid “AWS stealing our innovation/lunch/…”, (and it is always AWS set up as the bogeyman in these stories), is particularly unfortunate here, because it just isn’t true for Postgres. AWS, and Azure, employ big teams of community contributors to work on open source Postgres. The progress on statistics management, asynchronous IO, and vacuum in Postgres 18 are, among others, thanks to these teams’ efforts.&lt;/p>
&lt;p>No matter how positive the involvement of the hyperscalers may be for Postgres, there are organisations who will prefer to run their own databases. On-premises hosting is a clear choice for organisations with big facilities capabilities, capital-centric budgeting, extreme requirements and predictable, always on workloads. Many of these organisations are publicly funded scientific projects. It would be great if there were broad-based open source solutions to meet their data management needs.&lt;/p>
&lt;h3 id="doing-better-together">Doing better, together&lt;/h3>
&lt;p>At PGConf in Riga the Percona team took a few, early steps towards building a joint effort to deliver the components of such a solution. I hope that the big, open managers of structured scientific data (or their subcontractors, depending on their engagement model) and a few vendors can come together to build event data compression, columnar storage, and all the other bits which can be implemented as extensions.&lt;/p>
&lt;p>The current Postgres extensions and forks for scale out systems were built on older versions of Postgres, so they had to build features which now exist in core Postgres. Their implementation of partitioning, for instance, differs subtly from the capabilities now available in modern Postgres. As feature-specific extensions can take over capabilities which are currently intertwined with sharding (like compression in Timescale or columnar storage in Citus), users will be less locked in to vertical stacks of features, some useful to them and some not. Simple sharding can then become a proxy (like pgDog), an automation on DDL on a gateway server or even a core Postgres feature.&lt;/p>
&lt;p>Which leaves those special cases where moving data between shards during execution is key to performance. This is mattering less with ever bigger servers, improving Postgres parallelism and tools like DuckDB - but when it matters it still really matters. Here the sons of the ‘plum - CloudberryDB and WarehousePG, forked from Greenplum when it closed source - work their magic (hat tip to Jimmy Angelakos for the “the ‘plum” contraction). Managing that particular capability will always be a big, complex code base. If the patches carried to make it happen shrink as Postgres and extensions fill the gap, we’ll have a more sustainable route to all good database things being openly available.&lt;/p></content:encoded><author>Alastair Turner</author><category>PostgreSQL</category><category>Opensource</category><category>pg_alastair</category><category>Community</category><media:thumbnail url="https://percona.community/blog/2025/11/cover-map-blue_hu_209117837932fc65.jpg"/><media:content url="https://percona.community/blog/2025/11/cover-map-blue_hu_bdfd6ce3de2b5882.jpg" medium="image"/></item><item><title>OAuth, OIDC, validators, what is all this about?</title><link>https://percona.community/blog/2025/11/07/oauth-oidc-validators/</link><guid>https://percona.community/blog/2025/11/07/oauth-oidc-validators/</guid><pubDate>Fri, 07 Nov 2025 10:00:00 UTC</pubDate><description>Somebody might tell you, “let’s configure PostgreSQL 18 with OIDC, it should be simple, only takes a few minutes!” And that might be the case if you already have an OIDC provider set up and know all the details about the protocols, configurations, and possible issues. Or it might take much longer if you just open your favorite search engine and type “What is this OIDC stuff about?”</description><content:encoded>&lt;p>Somebody might tell you, “let’s configure PostgreSQL 18 with OIDC, it should be simple, only takes a few minutes!”
And that might be the case if you already have an OIDC provider set up and know all the details about the protocols, configurations, and possible issues.
Or it might take much longer if you just open your favorite search engine and type “What is this OIDC stuff about?”&lt;/p>
&lt;p>In this series of blog posts, I’ll try to help with this task.
First, by clearing up all the terminology and details in this article.
Later, I’ll provide vendor-specific setup instructions for some of the popular providers, using our fully open source &lt;code>pg_oidc_validator&lt;/code> plugin.&lt;/p>
&lt;h3 id="oauth-20-oidc-whats-even-the-difference">OAuth 2.0, OIDC, what’s even the difference?&lt;/h3>
&lt;p>From news and other sources you might have heard that PostgreSQL 18 now has support for OIDC.
But if you look at the &lt;a href="https://www.postgresql.org/docs/current/auth-oauth.html" target="_blank" rel="noopener noreferrer">PostgreSQL documentation about it&lt;/a>, or the variables/configuration options PostgreSQL provides, it’s clear that is about OAuth everywhere.&lt;/p>
&lt;p>People often use them interchangeably, because the two are closely related:
OIDC is built on top of OAuth.
However, they serve different purposes.&lt;/p>
&lt;pre class="mermaid">
flowchart TB
OAuth[OAuth 2.0&lt;br/>Authorization Protocol]
OIDC[OpenID Connect OIDC&lt;br/>Authentication Layer]
OAuth --> OIDC
OAuthQ["Can user X access&lt;br/>resource Y?"]
OIDCq["Who is this user?"]
OAuth -.->|Answers| OAuthQ
OIDC -.->|Answers| OIDCq
style OAuth fill:#e3f2fd
style OIDC fill:#f3e5f5
style OAuthQ fill:#fff,stroke:#1976d2,stroke-dasharray: 5 5
style OIDCq fill:#fff,stroke:#7b1fa2,stroke-dasharray: 5 5
&lt;/pre>
&lt;h4 id="authorization-is-not-authentication">Authorization is not Authentication&lt;/h4>
&lt;p>The “Auth” in OAuth is about Authorization, not Authentication as people often believe – and specifically about remote, distributed authorization involving multiple applications.
For example, somebody might store pictures on some cloud storage, and wants to use a photo editing application written and maintained by a different company.
This EditorApp reaches out to the CloudStorage, and says “Can you provide me the pictures please?”
After this question, CloudStorage has to figure out if it is allowed to do so or not.&lt;/p>
&lt;p>OAuth was designed to handle situations like this – managing permissions in a complex online environment.
However, EditorApp might also ask the question, “Hey, I want to display the name of the user I’m working with. Can you tell me who I am working with?”
And now we’ve leaped into the context of Authentication.
OIDC, or OpenID Connect, is a protocol built on top of OAuth to answer questions like this – to provide information about who the user is.&lt;/p>
&lt;h3 id="which-of-them-do-we-need-with-postgresql">Which of them do we need with PostgreSQL?&lt;/h3>
&lt;p>The question with PostgreSQL and similar software using OAuth/OIDC is a bit different:
we want to answer, “somebody is trying to log in – can I allow this login to proceed?”&lt;/p>
&lt;p>Is this the right question, or am I oversimplifying things?
Shouldn’t we be asking, “who is trying to log in?”
Sometimes we do.
The most common setup will likely ask both: “can this login proceed, and if yes, who is the user?”&lt;/p>
&lt;p>But not necessarily always.
It’s perfectly valid to configure a server to use this login flow only for administrators, where anybody who is allowed to use it gets associated with an internal admin account.
In this scenario, we don’t care about the identity provided by the external provider:
if the user has a valid access token, we treat them as our admin user and proceed accordingly.&lt;/p>
&lt;p>To handle this specific workflow, OAuth is enough:
all we have to do is check if the access token is allowed to access the PostgreSQL server, and if yes, the login can proceed as the admin user.
This is however a limited, specific use case, not the generic scenario, where we also want to figure out who is the user on the provider side.&lt;/p>
&lt;p>And there are also limitations on the server and client side:&lt;/p>
&lt;ul>
&lt;li>When using our validator plugin, it supports the more generic scenario. It has to work with user identities from the provider, so it requires OIDC features.
That’s why we called it &lt;code>pg_oidc_validator&lt;/code> and not &lt;code>pg_oauth_validator&lt;/code>.&lt;/li>
&lt;li>While the server and wire protocol can work with any OAuth flow, currently the only client that implements a login mechanism using it is &lt;code>libpq&lt;/code> (and with that, the &lt;code>psql&lt;/code> command).
And while the &lt;code>psql&lt;/code> command also uses parameters with OAuth in their name, internally it relies on a feature called OIDC discovery – which, as the name suggests, is part of the OIDC standard, not OAuth.&lt;/li>
&lt;/ul>
&lt;p>To summarize: in practice, right now anyone who wants to log in to a PostgreSQL server using OAuth and &lt;code>psql&lt;/code> has to use a provider that also supports the OIDC protocol.
In practice this isn’t a restriction, since OIDC is commonly supported.&lt;/p>
&lt;h3 id="why-do-we-need-this-validator">Why do we need this validator?&lt;/h3>
&lt;blockquote>
&lt;p>Why do we need to use a plugin to validate things?
Many websites and apps implement login with OIDC providers, and they don’t need separate validators, so:
Why can’t PostgreSQL do everything internally in the core?&lt;/p>&lt;/blockquote>
&lt;p>To answer these questions, we have to understand that PostgreSQL in this context isn’t an application in itself – it’s a part of a complex infrastructure.
Even &lt;code>libpq&lt;/code>, mentioned above, isn’t strictly part of the picture.
There are completely independent implementations of the PostgreSQL wire protocol, which can all implement the client-side flow completely differently.&lt;/p>
&lt;p>This bigger system that happens to use PostgreSQL might also use other services that need authorization.
The EditorApp in our earlier example might use PostgreSQL as its database.
And that means, after authenticating the user, it has to talk to two different services:&lt;/p>
&lt;ul>
&lt;li>CloudStorage, to access the photos on behalf of the user&lt;/li>
&lt;li>PostgreSQL, to access the database on behalf of the user&lt;/li>
&lt;/ul>
&lt;p>Since users like seamless experiences, the developers of EditorApp want to log the user in only once, internally as part of their application, and then forward this information to both CloudStorage and PostgreSQL.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant User
participant EditorApp
participant Provider
participant CloudStorage
participant PostgreSQL
User->>EditorApp: Login
EditorApp->>Provider: Request OAuth/OIDC authentication
Provider->>User: Show login page
User->>Provider: Enter credentials
Provider->>EditorApp: Return access token
Note over EditorApp: User authenticated once,&lt;br/>token used for multiple services
EditorApp->>CloudStorage: Access photos (with token)
CloudStorage->>Provider: Validate token
Provider->>CloudStorage: Token valid
CloudStorage->>EditorApp: Return photos
EditorApp->>PostgreSQL: Connect to database (with token)
PostgreSQL->>PostgreSQL: Validator checks token
PostgreSQL->>EditorApp: Connection established
&lt;/pre>
&lt;p>This showcases an important difference compared to “simple” websites and applications:
PostgreSQL doesn’t own the entire authentication flow.
It receives information – what the OAuth standard calls an &lt;code>access token&lt;/code> – and needs to use this to complete its internal authentication/authorization flow.
To do this, it has to validate the access token and answer the question:
“was this token really created by the issuer I trust?”&lt;/p>
&lt;p>There’s nothing new about this question.
Big cloud providers like Google, Microsoft, and many others all do SSO (Single sign-on).
If you log in to their webmail service, you can also open their cloud storage, calendar, or other services, and you’ll be similarly authenticated and authorized.&lt;/p>
&lt;p>However, there is one very important difference in the above example compared to PostgreSQL:
they all only work with their own users and their own tokens.&lt;/p>
&lt;p>You might think this is still an easy problem: OAuth and OIDC are standards, so all we have to do is read the related parts about how validation works and implement our validation flow accordingly.
Except that the standards don’t say anything about it.&lt;/p>
&lt;p>These standards never define what an access token is.
It can be something completely opaque, only interpretable by the original issuer.
Or it can be something completely transparent – a proper JSON document with a digital signature that guarantees it was issued by the issuer, usually called a &lt;code>JWT&lt;/code> (JSON Web Token).&lt;/p>
&lt;p>Many OAuth providers implement &lt;code>JWT&lt;/code> tokens, but not all of them.
And even with &lt;code>JWT&lt;/code> tokens, there are differences between providers.
Since it’s not standardized, the content might differ between vendors.
There’s also the question of signature validation, where some providers have differences and require special handling.&lt;/p>
&lt;p>And this is where a plugin comes into the picture.
The PostgreSQL maintainers decided they don’t want to add vendor-specific code into the core.
Implementing &lt;code>JWT&lt;/code> access tokens with the most commonly used structures could have been a solution, but that would have meant PostgreSQL had no way to support providers that were completely standards-compliant but implemented access tokens differently.&lt;/p>
&lt;p>Even with &lt;code>JWT&lt;/code> tokens, we have to think about token revocation.
&lt;code>JWT&lt;/code> tokens have a specified lifetime.
When we validate them strictly based on the digital signature, we also have to check if we’re still within the allocated time.
The protocols have a mechanism for refreshing these tokens so that users can stay logged in without repeating the process every time the tokens expire.&lt;/p>
&lt;p>But sometimes administrators or the security team might discover a breach – that somebody got hold of an access token that’s still valid for hours or days – and decide to revoke it.
They can’t change the tokens already circulating the network, but they can tell their authorization server to start rejecting the token, even if it’s still within its allowed lifetime.&lt;/p>
&lt;p>If a validator relies solely on validating the signature of the token, it will still authorize users with these revoked tokens as long as their lifetime allows.
If it performs a request to the server to check if the token is still usable, it might provide a more secure experience at the cost of additional HTTP requests.
Alternatively, some providers allow applications to subscribe to broadcasts where they announce revocations – so instead of actively querying each token, they can keep a list of which tokens they aren’t allowed to authorize, as this list is typically short.&lt;/p>
&lt;p>Unfortunately, with these questions we’ve again arrived at “vendor-specific” territory, where a validator has to work differently for different services.
It’s also a user choice:&lt;/p>
&lt;ul>
&lt;li>On a service where logins are rare but security is a top priority, it might make sense to do explicit queries every time.&lt;/li>
&lt;li>But on a server processing thousands of login requests every second, these additional requests might slow things down too much. Administrators might decide not to do them, or to cache the results for some period instead.&lt;/li>
&lt;/ul>
&lt;p>With all these details and choices, it’s definitely better to leave the options open so everyone can select a validator suited for their specific needs.&lt;/p>
&lt;h3 id="what-does-pg_oidc_validator-offer">What does pg_oidc_validator offer?&lt;/h3>
&lt;p>The Percona validator in its current form focuses on providers working with &lt;code>JWT&lt;/code> tokens.
We can’t guarantee it works with all providers using &lt;code>JWT&lt;/code> tokens – as mentioned above, the exact format of these tokens isn’t standardized – but we implemented logic that can handle all the providers we tested, in some cases allowing customization with configuration variables.&lt;/p>
&lt;p>In the future, we might add support for providers using opaque tokens or additional optional checks for token revocation, but these weren’t in scope for the first version.&lt;/p>
&lt;p>If you try it out, and find any issues, or have any suggestions, miss some features, &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/issues" target="_blank" rel="noopener noreferrer">please provide us feedback on Github&lt;/a>!&lt;/p>
&lt;h3 id="the-future">The future&lt;/h3>
&lt;p>OAuth support in PostgreSQL is new and only implements the bare minimum.
Client and application support is also in its early days – adoption will take time.
It can already be useful for some use cases, but it might still be too limited for others.&lt;/p>
&lt;p>I can’t predict how the code will change in the server itself, but there are certainly many improvements that can be made.
I also want to avoid confusion and clear up some things related to my previous examples – things that aren’t currently supported but might be in the future:&lt;/p>
&lt;ul>
&lt;li>Even if a validator plugin checks for revocation, that check is only done during the login process.
If a user is already logged in to PostgreSQL and the token is revoked, the user will stay logged in – the validator can’t evict them from the server.&lt;/li>
&lt;li>Similarly, while the protocol has this concept of access token lifetime and how applications using the token can refresh it, this is currently ignored by PostgreSQL.
Tokens are validated during the login process, and we check the validity, including the lifetime of the token, during that time.
We don’t try to refresh these tokens later, and we don’t evict users when the token expires – currently there’s no infrastructure for that.&lt;/li>
&lt;li>In the examples above, CloudStorage was able to decide which photos EditorApp can access.
OAuth is about authorization, and it could be used for authorization within PostgreSQL – but this isn’t the case currently.
Validators have to map access tokens to internal users present in the PostgreSQL database. Other than this, the information provided by the OAuth Provider has no interaction with the internal permissions within PostgreSQL (grants).&lt;/li>
&lt;li>In the example above, we described a situation where an application used PostgreSQL together with other services using the same OAuth provider.
While this is a possible use case, it’s also possible that an application only needs an OIDC login flow to access PostgreSQL. In that case, it would be possible to provide better security guarantees by integrating the login flow deeply into PostgreSQL, especially when using more recent OAuth/OIDC extensions such as &lt;a href="https://www.rfc-editor.org/rfc/rfc7636.html" target="_blank" rel="noopener noreferrer">PKCE&lt;/a> (Proof Key for Code Exchange).&lt;/li>
&lt;/ul>
&lt;p>Similarly, our &lt;code>pg_oidc_validator&lt;/code> is a pre-release prototype.
While it already performs &lt;code>JWT&lt;/code>-based authentication and authorization, it currently doesn’t support opaque token providers at all, has no internal caches, and doesn’t check for revoked tokens.
Also, some of the features mentioned above would be better with integrated core support, but could technically also be implemented as part of a validator plugin. This would provide the additional benefit that users wouldn’t have to wait for newer PostgreSQL versions for the new feature – it would be enough to upgrade the validator plugin to a newer version.&lt;/p>
&lt;p>So there are lots of improvement opportunities for both parts. Stay tuned and follow the changelogs!&lt;/p></content:encoded><author>Zsolt Parragi</author><category>PostgreSQL</category><category>Opensource</category><category>pg_zsolt</category><category>OIDC</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2025/11/oidc1_hu_36e3c087f2e02705.jpg"/><media:content url="https://percona.community/blog/2025/11/oidc1_hu_cf96493e0a4d565d.jpg" medium="image"/></item><item><title>Encryption support in PMM Dump</title><link>https://percona.community/blog/2025/10/30/encryption-support-in-pmm-dump/</link><guid>https://percona.community/blog/2025/10/30/encryption-support-in-pmm-dump/</guid><pubDate>Thu, 30 Oct 2025 11:00:00 UTC</pubDate><description>The pmm-dump client utility performs a logical backup of the performance metrics collected by the PMM Server and imports them into a different PMM Server instance. PMM Dump allows you to share monitoring data collected by your PMM server with the Percona Support team securely.</description><content:encoded>&lt;p>The &lt;code>pmm-dump&lt;/code> client utility performs a logical backup of the performance metrics collected by the PMM Server and imports them into a different PMM Server instance. PMM Dump allows you to share monitoring data collected by your PMM server with the Percona Support team securely.&lt;/p>
&lt;p>Up until now dumps, created by the tool, were not encrypted. It was possible to encrypt them after they are done but this required additional actions from the user.&lt;/p>
&lt;p>Starting from the upcoming PMM Dump version 0.8.0-ga released on October 29, 2025, dumps are encrypted by default.&lt;/p>
&lt;h2 id="key-points">Key points&lt;/h2>
&lt;ul>
&lt;li>Dump files are encrypted by default with AES-256-based encryption.&lt;/li>
&lt;li>An auto-generated password is produced for each encrypted dump; it is printed at the end of the export operation or can be written to a file with &lt;code>--pass-filepath&lt;/code>.&lt;/li>
&lt;li>You can provide a custom password with &lt;code>--pass&lt;/code>.&lt;/li>
&lt;li>Disable encryption with &lt;code>--no-encryption&lt;/code> only when you understand the risks.&lt;/li>
&lt;li>By default, for encrypted dumps, export logging to STDOUT is suppressed; use &lt;code>--no-just-key&lt;/code> to override.&lt;/li>
&lt;/ul>
&lt;h2 id="why-this-matters">Why this matters&lt;/h2>
&lt;p>Encrypting PMM dumps prevents accidental exposure of monitoring and query data that may contain sensitive information (query text, hostnames, metrics). It brings PMM Dump in line with secure data-handling best practices and simplifies safe sharing with Percona Support.&lt;/p>
&lt;h2 id="quick-examples">Quick examples&lt;/h2>
&lt;p>Export (encryption enabled by default):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-dump export --pmm-url='https://admin:admin@127.0.0.1' --allow-insecure-certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Password: ****************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls pmm-dump-&lt;TIMESTAMP>.tar.gz.enc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Provide a custom password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-dump export --pmm-url='https://admin:admin@127.0.0.1' --pass='My$trongP@ss'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Save auto-generated password to file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-dump export --pmm-url='https://admin:admin@127.0.0.1' --pass-filepath=/tmp/pmm-dump.pass&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Disable encryption (not recommended):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-dump export --pmm-url='https://admin:admin@127.0.0.1' --no-encryption&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Import an encrypted dump:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-dump import --pmm-url='https://admin:admin@127.0.0.1' --allow-insecure-certs \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--dump-path=pmm-dump-1758017090.tar.gz.enc --pass='My$trongP@ss'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Decrypt an encrypted dump (if needed):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ openssl enc -d -aes-256-ctr -pbkdf2 -in dump.tar.gz.enc -out dump.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="recommendations">Recommendations&lt;/h2>
&lt;ul>
&lt;li>Prefer leaving encryption enabled.&lt;/li>
&lt;li>Use &lt;code>--pass-filepath&lt;/code> to store passwords securely rather than relying on terminal output.&lt;/li>
&lt;li>Transfer encrypted archives over secure channels (SCP/SFTP) and share passwords via secure out-of-band channels.&lt;/li>
&lt;/ul>
&lt;h2 id="availability">Availability&lt;/h2>
&lt;p>Encryption support is included starting in the recent PMM Dump 0.8.0-ga release. Check your PMM Dump version (&lt;code>pmm-dump version&lt;/code>) and the docs for exact version details.&lt;/p>
&lt;h2 id="additional-information">Additional information&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://percona.com/get/pmm-dump" target="_blank" rel="noopener noreferrer">Latest version for x86_64 platforms&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/Percona-Lab/percona-on-arm/releases/tag/v0.12" target="_blank" rel="noopener noreferrer">ARM binaries&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/pmm-dump-documentation/" target="_blank" rel="noopener noreferrer">PMM Dump Documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/pmm-dump" target="_blank" rel="noopener noreferrer">GitHub repository&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Sveta Smirnova</author><category>PMM Dump</category><category>PMM</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/10/Sveta-PMM-Dump_hu_4169866126775b73.jpg"/><media:content url="https://percona.community/blog/2025/10/Sveta-PMM-Dump_hu_b118ee6552823d62.jpg" medium="image"/></item><item><title>Say Hello to OIDC in PostgreSQL 18!</title><link>https://percona.community/blog/2025/10/22/say-hello-to-oidc-in-postgresql-18/</link><guid>https://percona.community/blog/2025/10/22/say-hello-to-oidc-in-postgresql-18/</guid><pubDate>Wed, 22 Oct 2025 11:00:00 UTC</pubDate><description>If you’ve ever wondered how to set up OpenID Connect (OIDC) authentication in PostgreSQL, the wait is almost over.</description><content:encoded>&lt;p>If you’ve ever wondered how to set up OpenID Connect (OIDC) authentication in PostgreSQL, the wait is almost over.&lt;/p>
&lt;p>We’ve spent some time exploring what it would take to make OIDC easier and more reliable to use with PostgreSQL. And now, we’re happy to share the first results of that work.&lt;/p>
&lt;h3 id="why-oidc-and-why-now">Why OIDC, and why now?&lt;/h3>
&lt;p>We’ve spoken to some of our customers and noticed a trend of moving away from LDAP to OIDC. Our MongoDB product is already providing OIDC integration and the team working on PostgreSQL products saw an opportunity coming with PostgreSQL 18.&lt;/p>
&lt;p>As some of you may have noticed &lt;strong>PostgreSQL 18&lt;/strong> has introduced improvements to authentication that include OAuth 2.0 support. It’s a small step from OAuth 2.0 to OIDC, which builds directly on top of it. So we set out to test PostgreSQL 18’s integration with one of the most common OIDC providers: Okta.&lt;/p>
&lt;p>That’s when we discovered a missing piece. While PostgreSQL 18 includes the underlying support for OAuth 2.0, it still lacks a validator library required to successfully complete an OIDC configuration.
So… we built it.&lt;/p>
&lt;h3 id="closing-the-gap-for-enterprise-needs">Closing the gap for enterprise needs&lt;/h3>
&lt;p>Percona is known for our Support, Managed, and Consulting services for open source databases. But while our services are commercial, our mission remains deeply open source.&lt;/p>
&lt;p>We don’t differentiate users by the size of their wallets. We want everyone using open source databases to succeed, grow, and benefit from the same quality foundations that enterprises rely on. Services is what finances our open source investments, what we believe is truly honest and sustainable open source development.&lt;/p>
&lt;p>OIDC is a great example of how real-world enterprise needs and community innovation come together. PostgreSQL 18 introduced OAuth thanks to community efforts, but OIDC that helps organizations handle compliance and scale was still not supported. With the work on OIDC validator we want to close the gap between these two while keeping the solution open source.&lt;/p>
&lt;h3 id="the-power-is-in-testing">The power is in testing&lt;/h3>
&lt;p>It would be great if integrating with one OIDC provider meant it works with all of them. Unfortunately, real world implementations differ, sometimes significantly.
That’s why compatibility testing matters. That’s also why often particular providers we find require independent approach and some custom handling in the code.&lt;/p>
&lt;p>So far, we’ve done some preliminary tests of the library. It’s not production-ready yet. It’s a first release that’s shared with users and Community to get feedback while in the meantime our engineers will put it through rigorous testing regimen.
We’ve so far tested the validator library compatibility with:&lt;/p>
&lt;p>    ✅ &lt;strong>Okta&lt;/strong>&lt;/p>
&lt;p>    ✅ &lt;strong>Ping Identity&lt;/strong>&lt;/p>
&lt;p>    ✅ &lt;strong>Keycloak&lt;/strong>&lt;/p>
&lt;p>    ✅ &lt;strong>Microsoft Entra ID (Azure AD)&lt;/strong>&lt;/p>
&lt;p>We’re aware it’s not yet compatible with Google’s OIDC implementation, which has a few unique quirks, but that’s on our roadmap for future work.
The broader the testing, the stronger the solution. This is where we hope the Community can join us.&lt;/p>
&lt;h3 id="try-the-oidc-validator-library-now">Try the OIDC validator library now!&lt;/h3>
&lt;p>We’re excited to share that the first release of the OIDC &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/releases/tag/latest" target="_blank" rel="noopener noreferrer">validator library is now available&lt;/a> for your feedback.
&lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/tree/main" target="_blank" rel="noopener noreferrer">The repository includes&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>Basic setup instructions in the README,&lt;/li>
&lt;li>Introductory documentation for getting started, and&lt;/li>
&lt;li>Links to examples and test configurations.&lt;/li>
&lt;/ul>
&lt;p>More detailed guides, including how OIDC works under the hood and how to use it in real PostgreSQL deployments, as well as direct integration guides are coming soon in follow up blog posts and documentation articles.&lt;/p>
&lt;h3 id="wed-love-your-feedback">We’d love your feedback&lt;/h3>
&lt;p>If you’re experimenting with PostgreSQL 18 or exploring modern authentication options, give the &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/releases/tag/latest" target="_blank" rel="noopener noreferrer">OIDC validator library&lt;/a> a try and &lt;a href="https://github.com/Percona-Lab/pg_oidc_validator/discussions" target="_blank" rel="noopener noreferrer">let us know what you think&lt;/a>!
Your input will help us make this capability more robust, portable, and enterprise-ready while keeping it open source and accessible to everyone.&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>OIDC</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2025/10/Slonik-passport_hu_1d0306aedc51638a.jpg"/><media:content url="https://percona.community/blog/2025/10/Slonik-passport_hu_53386beecc9d88ed.jpg" medium="image"/></item><item><title>Keep Calm - TDE for PostgreSQL 18 Is on Its Way!</title><link>https://percona.community/blog/2025/10/15/keep-calm-tde-for-postgresql-18-is-on-its-way/</link><guid>https://percona.community/blog/2025/10/15/keep-calm-tde-for-postgresql-18-is-on-its-way/</guid><pubDate>Wed, 15 Oct 2025 11:00:00 UTC</pubDate><description>If you’ve been following the buzz around PostgreSQL, you’ve probably already heard that database level open source data-at-rest encryption is now available thanks to the Transparent Data Encryption (TDE) extension available in the Percona Distribution for PostgreSQL. So naturally, the next question is:</description><content:encoded>&lt;p>If you’ve been following the buzz around PostgreSQL, you’ve probably already heard that database level open source data-at-rest encryption is now available thanks to the Transparent Data Encryption (TDE) extension available in the Percona Distribution for PostgreSQL.
So naturally, the next question is:&lt;/p>
&lt;blockquote>
&lt;p>Where’s Percona Distribution for PostgreSQL 18?&lt;/p>&lt;/blockquote>
&lt;h3 id="the-short-answer">The short answer:&lt;/h3>
&lt;p>It’s coming.&lt;/p>
&lt;h3 id="the-slightly-longer-one">The slightly longer one:&lt;/h3>
&lt;p>It’s &lt;em>taking a bit of time, for all the right reasons.&lt;/em>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_ac57a8915089384d.webp 768w, https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_16221c725a91f42a.webp 480w, https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_a77e081389627e50.webp 1400w" src="https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_a77e081389627e50.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="why-the-delay">Why the delay?&lt;/h3>
&lt;p>We’ve been laser-focused on advancing pg_tde maturity, making it ready for your production workloads. We succeeded and now you can use Percona Distribution for PostgreSQL together with TDE on PROD!
The next steps for us are:
1 Merging the changes to PostgreSQL 18.
2 Ensuring that the patches making TDE on PostgreSQL possible are accepted by PostgreSQL Community.&lt;/p>
&lt;p>As part of both of these efforts, we took the time to thoroughly test TDE against PostgreSQL 18. During that process, we found that to use TDE on PostgreSQL 18 some extra work is needed to ensure the new &lt;a href="https://wiki.postgresql.org/wiki/AIO" target="_blank" rel="noopener noreferrer">Asynchronous I/O (AIO)&lt;/a> feature works as designed.
Since TDE extends the Storage Manager (SMGR), it needs to integrate smoothly with changes introduced by AIO and that’s something we focus on now and are definitely do not want to rush.&lt;/p>
&lt;p>Percona stands for quality of services and products. Databases are the fundaments of the information systems, outside of security the most important values are stability and availability. To respect these values we chose not to push a major release just for the sake of timing, but instead to take the time to do things properly.&lt;/p>
&lt;p>We’re planning to release TDE with the first minor patch of PostgreSQL 18, currently &lt;a href="https://www.postgresql.org/developer/roadmap/" target="_blank" rel="noopener noreferrer">scheduled for November 13&lt;/a>.&lt;/p>
&lt;h3 id="taking-our-time-responsibly">Taking our time responsibly&lt;/h3>
&lt;p>Most production users don’t deploy brand new major releases on day one. Typically before using a new major version on production we’ve seen a minimum on 1-2 minor releases for even the users we’ve seen fastest to adopt new versions.&lt;/p>
&lt;p>That gives us a valuable window to make sure everything is rock-solid before it reaches your clusters. We’re using that time wisely: testing, aligning, and polishing so that when you upgrade to PostgreSQL 18 with Percona TDE, it’ll feel like it was part of the core all along.&lt;/p>
&lt;h3 id="oh-and-something-new-is-brewing">Oh, and something new is brewing…&lt;/h3>
&lt;p>As PostgreSQL 18 introduces &lt;a href="https://www.postgresql.org/docs/current/auth-oauth.html" target="_blank" rel="noopener noreferrer">OAuth Authorization/Authentication&lt;/a> we tried using it with OpenID Connect (OIDC). OIDC is an identity layer built on top of OAuth 2.0, adding user authentication to OAuth’s authorization capabilities.&lt;/p>
&lt;p>We tried using OIDC from a number of providers and came to a conclusion that there’s currently no easy way of using it with PostgreSQL 18. The key missing component to make it work is a validator library that would allow to validate the identity tokens.&lt;/p>
&lt;p>I’m happy to share what we started working on OIDC support for PostgreSQL, built on top of the OAuth support introduced in PG18. The first beta release of our OIDC validator library is coming soon, stay tuned for that!&lt;/p>
&lt;h3 id="how-to-help">How to help?&lt;/h3>
&lt;p>Easy, share your feedback!&lt;/p>
&lt;ul>
&lt;li>Have you tried pg_tde? &lt;a href="https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82" target="_blank" rel="noopener noreferrer">Tell us what worked, what didn’t, and how it felt in real use&lt;/a>.&lt;/li>
&lt;li>Looking for OIDC in PostgreSQL? We’d love your thoughts once the beta drops.&lt;/li>
&lt;li>Using pg_stat_monitor? &lt;a href="https://percona.community/blog/2025/08/13/pg_stat_monitor-needs-you-join-the-feedback-phase/" target="_blank" rel="noopener noreferrer">Share how you’re using it and helps us make it better&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>We’re invested to making open source databases more secure, flexible and ready for whatever you throw at them one careful step at a time.&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/10/Jan-Cover-TDE-PG18_hu_a3e4f5aedd5cd070.jpg"/><media:content url="https://percona.community/blog/2025/10/Jan-Cover-TDE-PG18_hu_9b5143e8824e2aa5.jpg" medium="image"/></item><item><title>Audit Log Filters Part II</title><link>https://percona.community/blog/2025/10/08/audit-log-filters-part-ii/</link><guid>https://percona.community/blog/2025/10/08/audit-log-filters-part-ii/</guid><pubDate>Wed, 08 Oct 2025 00:00:00 UTC</pubDate><description>In my first post on the MySQL 8.4 Audit Log Filter component, I covered how to install the component and configure a basic filter that captures all events. The Audit Log Filter framework offers a highly granular and configurable auditing mechanism, enabling administrators to log specific events based on criteria such as user, host, or event type. This selective approach enhances observability, supports compliance initiatives, and minimizes unnecessary logging overhead.</description><content:encoded>&lt;p>In my first post on the &lt;a href="https://percona.community/blog/2025/09/18/audit-log-filter-component/" target="_blank" rel="noopener noreferrer">MySQL 8.4 Audit Log Filter component&lt;/a>, I covered how to install the component and configure a basic filter that captures all events. The Audit Log Filter framework offers a highly granular and configurable auditing mechanism, enabling administrators to log specific events based on criteria such as user, host, or event type. This selective approach enhances observability, supports compliance initiatives, and minimizes unnecessary logging overhead.&lt;/p>
&lt;p>In this follow-up, we’ll take a deeper technical look at defining and optimizing audit log filters to capture only the most relevant database activities—delivering actionable audit data while significantly reducing noise and log volume.&lt;/p>
&lt;h3 id="example-1">Example 1&lt;/h3>
&lt;p>Audit all events:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_filter('log_all_events', '{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "filter": {"log": true}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once this filter is created and assigned to a user (for example, with SELECT audit_log_filter_set_user(’%’, ’log_all_events’);), every database event triggered by that user—or by all users if % is used—will be written to the audit log file.&lt;/p>
&lt;p>In short:&lt;/p>
&lt;p>This is the most permissive audit configuration possible. It’s typically used:&lt;/p>
&lt;ol>
&lt;li>As a baseline test to verify that the audit log component is working.&lt;/li>
&lt;li>In diagnostic or forensic scenarios where full visibility is required.&lt;/li>
&lt;/ol>
&lt;p>For production environments, however, it’s recommended to create more selective filters (e.g., by event class, command type, or user) to reduce log volume and improve performance. Which we will go into more detail in the upcoming examples.&lt;/p>
&lt;h3 id="example-2">Example 2&lt;/h3>
&lt;p>Log table access:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">select audit_log_filter_set_filter('log_table_access', '{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "filter": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "class": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "table_access" },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "connection" },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "general" }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="included-event-classes">Included Event Classes&lt;/h4>
&lt;ol>
&lt;li>
&lt;p>table_access
Logs events when MySQL reads from or writes to tables.&lt;/p>
&lt;ul>
&lt;li>Useful for tracking which users or applications are accessing specific tables.&lt;/li>
&lt;li>Helps in auditing data access patterns and detecting unauthorized data reads/writes.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>connection
Logs connection-related events such as user logins, logouts, and failed authentication attempts.&lt;/p>
&lt;ul>
&lt;li>Important for tracking session activity and security auditing.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>general
Logs general query execution events—like statements sent to the server (e.g., SELECT, INSERT, UPDATE, etc.).&lt;/p>
&lt;ul>
&lt;li>Useful for general SQL activity auditing.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h4 id="what-it-does-functionally">What It Does Functionally&lt;/h4>
&lt;p>After this filter is defined and assigned to a user or host (for example, with
SELECT audit_log_filter_set_user(’%’, ’log_table_access’);), MySQL will only log events that fall into one of these three classes.&lt;/p>
&lt;p>All other event types—like administrative commands, stored program executions, or system-level actions—will be excluded from the audit log.&lt;/p>
&lt;h4 id="why-use-this-filter">Why use this filter&lt;/h4>
&lt;p>This configuration strikes a balance between completeness and efficiency:&lt;/p>
&lt;ul>
&lt;li>Captures key operational and access-related activity.&lt;/li>
&lt;li>Avoids excessive log volume from irrelevant events.&lt;/li>
&lt;li>Suitable for data access auditing, security monitoring, and compliance logging.&lt;/li>
&lt;/ul>
&lt;p>In short, log_table_access provides targeted visibility into table usage, connections, and general query activity—ideal for environments where tracking who accessed what data is more important than recording every internal event.&lt;/p>
&lt;h3 id="example-3">Example 3&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_filter('log_minimum', '{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "filter": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "class":
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [ { "name": "connection" }, { "name": "table_access", "event": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "delete"}, { "name": "insert"}, { "name": "update"} ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="included-event-classes-1">Included Event Classes&lt;/h4>
&lt;ol>
&lt;li>“class”: “connection”
&lt;ul>
&lt;li>Logs all connection-level events:&lt;/li>
&lt;li>connect: when a user logs in.&lt;/li>
&lt;li>disconnect: when a session ends.&lt;/li>
&lt;li>Failed logins and other connection-related actions.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Purpose: provides visibility into who connected, from where, and when.&lt;/p>
&lt;ol start="2">
&lt;li>“class”: “table_access” with “event”:
&lt;ul>
&lt;li>Limits logging to specific table access events:
&lt;ul>
&lt;li>“delete” when rows are deleted.&lt;/li>
&lt;li>“insert” when new rows are added.&lt;/li>
&lt;li>“update” when existing rows are modified.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Read operations (like SELECT) and metadata queries are excluded.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h4 id="what-it-does-functionally-1">What It Does Functionally&lt;/h4>
&lt;p>Once assigned to a user or host (e.g.
SELECT audit_log_filter_set_user(’%’, ’log_minimum’);), this filter will produce audit entries only when:&lt;/p>
&lt;ul>
&lt;li>A user connects or disconnects from MySQL.&lt;/li>
&lt;li>A user performs a DML (Data Manipulation Language) operation that changes data in a table.&lt;/li>
&lt;/ul>
&lt;p>All other events — such as simple SELECT queries, schema reads, or administrative commands — will be ignored.&lt;/p>
&lt;h4 id="why-use-this-filter-1">Why Use This Filter&lt;/h4>
&lt;p>This is a minimalist, high-value audit configuration. It’s designed to:&lt;/p>
&lt;ul>
&lt;li>Track security-relevant activity (connections and data changes).&lt;/li>
&lt;li>Meet compliance requirements with low performance overhead.&lt;/li>
&lt;li>Prevent excessive logging and disk usage.&lt;/li>
&lt;/ul>
&lt;p>In Short log_minimum is an efficient auditing strategy for production environments where you only need to know:&lt;/p>
&lt;ul>
&lt;li>Who accessed the database, and&lt;/li>
&lt;li>What data they changed.&lt;/li>
&lt;/ul>
&lt;p>It gives you essential accountability and change tracking without the overhead of logging every read or administrative event.&lt;/p>
&lt;h3 id="example-4">Example 4&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_filter('log_connections', '{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "filter": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "class": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "connection",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "event": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "connect"},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "disconnect"}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="included-event-classes-2">Included Event Classes&lt;/h4>
&lt;ol>
&lt;li>
&lt;p>“class”: “connection”&lt;/p>
&lt;ul>
&lt;li>This class captures events related to user sessions and authentication.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>“event”: [“connect”, “disconnect”]&lt;/p>
&lt;ul>
&lt;li>connect, Logged when a client establishes a connection to the MySQL server.
Includes details like username, host, client program, IP address, and connection method.&lt;/li>
&lt;li>disconnect, Logged when that client session ends or times out.
Useful for tracking session duration and identifying abnormal terminations.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Important Note on pre_authenticate Events&lt;/p>
&lt;ul>
&lt;li>The pre_authenticate events are not included in the examples above.&lt;/li>
&lt;li>These events occur before the MySQL server has received authentication information from the client—meaning no user account details are available at this stage of the connection lifecycle. Because of that, if a filter that includes pre_authenticate events is assigned to a specific user (rather than a wildcard like %) using audit_log_filter_set_user(), those events will not be filtered or logged.&lt;/li>
&lt;li>This behavior often leads to confusion, as users may expect pre_authenticate events to appear in user-specific logs. Several reports and support cases have been filed on this topic, but it is expected behavior due to the timing of authentication during connection initialization.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h4 id="why-use-this-filter-2">Why Use This Filter&lt;/h4>
&lt;p>This filter is particularly useful when you need to:&lt;/p>
&lt;ul>
&lt;li>Monitor user logins and logouts without recording query activity.&lt;/li>
&lt;li>Audit connection patterns (e.g., who connected, from where, and when).&lt;/li>
&lt;li>Maintain minimal log size and low overhead.&lt;/li>
&lt;li>Support security investigations or session tracking without performance impact.&lt;/li>
&lt;/ul>
&lt;p>In short the log_connections filter provides a focused, low-overhead auditing strategy that records only connection lifecycle events. It’s ideal for environments where you primarily need to know who connected to the database, when, and from where without capturing every SQL statement or table access.&lt;/p>
&lt;h3 id="example-5">Example 5&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_filter('log_full_table_access', '{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "filter": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "class": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "name": "connection",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "event": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "connect"},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "name": "disconnect"}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "name": "query",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "event": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "name": "start",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "log": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "or": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "select"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "insert"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "update"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "delete"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "truncate"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "create_table"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "alter_table"} },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "field": { "name": "sql_command_id", "value": "drop_table"} }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This statement defines a MySQL Audit Log Filter called log_full_table_access, which is designed to capture both connection activity and all table-related SQL operations — including reads, writes, and schema changes. It provides broad visibility into how users interact with tables in the database while filtering out unrelated or low-value events.&lt;/p>
&lt;h4 id="included-event-classes-3">Included Event Classes&lt;/h4>
&lt;p>After assigning it to users or hosts (e.g.
SELECT audit_log_filter_set_user(’%’, ’log_full_table_access’);), MySQL will log:&lt;/p>
&lt;ul>
&lt;li>Connection lifecycle events (connect, disconnect)&lt;/li>
&lt;li>All DML statements (SELECT, INSERT, UPDATE, DELETE, TRUNCATE)&lt;/li>
&lt;li>All DDL statements that create, modify, or remove tables (CREATE TABLE, ALTER TABLE, DROP TABLE)&lt;/li>
&lt;li>A full list of SQL_COMMANDS can be obtained from:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT NAME FROM performance_schema.setup_instruments WHERE NAME LIKE 'statement/sql/%' ORDER BY NAME;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| NAME |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_db |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_event |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_function |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_instance |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_procedure |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_resource_group |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_server |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_table |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_tablespace |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_user |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/alter_user_default_role |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/analyze |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/assign_to_keycache |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/begin |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/binlog |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/call_procedure |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/change_db |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/change_repl_filter |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/change_replication_source |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/check |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/checksum |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/commit |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| statement/sql/create_compression_dictionary |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">168 rows in set (0.07 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Everything else — administrative commands, stored procedure calls, replication control, etc. — will be excluded.&lt;/p>
&lt;h4 id="why-use-this-filter-3">Why Use This Filter&lt;/h4>
&lt;p>This filter offers a comprehensive audit view of how users interact with data and schema structures — perfect for compliance, forensic analysis, or access accountability.
It ensures that all table reads, writes, and structure changes are tracked without overwhelming the log with irrelevant internal events.&lt;/p>
&lt;p>In Short, log_full_table_access provides a broad but targeted audit scope:&lt;/p>
&lt;ul>
&lt;li>Tracks connections for user session context.&lt;/li>
&lt;li>Logs all table-level operations, both data and schema-related.&lt;/li>
&lt;li>Delivers complete visibility into how data is accessed and changed, making it ideal for security auditing and regulatory compliance scenarios.&lt;/li>
&lt;/ul>
&lt;h3 id="final-summary">Final Summary&lt;/h3>
&lt;p>The MySQL 8.4 Audit Log Filter component provides a powerful and flexible framework for controlling how database activity is captured and logged. By allowing administrators to define granular filters based on event class, event type, user, or host, it transforms auditing from an all-or-nothing process into a precisely tuned observability tool.&lt;/p>
&lt;p>In this post, we explored a range of filter examples—from the most permissive (log_all_events) to more focused configurations like log_minimum, log_connections, and log_full_table_access. Each serves a different operational or compliance purpose:&lt;/p>
&lt;ul>
&lt;li>log_all_events – Captures every event for baseline validation or forensic debugging.&lt;/li>
&lt;li>log_table_access – Balances visibility and performance by logging table, connection, and general query activity.&lt;/li>
&lt;li>log_minimum – Targets critical actions such as connections and data modifications, providing essential accountability with minimal overhead.&lt;/li>
&lt;li>log_connections – Focuses solely on login and logout events, ideal for lightweight session auditing.&lt;/li>
&lt;li>log_full_table_access – Delivers comprehensive insight into all table-level DML and DDL operations along with connection tracking, suitable for compliance and change auditing.&lt;/li>
&lt;/ul>
&lt;p>By tailoring filters to specific operational needs, administrators can significantly reduce log volume, improve performance, and focus on high-value security and compliance events. The result is a leaner, more informative audit log that provides actionable insight into how users and applications interact with your MySQL environment—without the burden of unnecessary data.&lt;/p>
&lt;h3 id="reference">Reference&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/8.4/audit-log-filter-overview.html" target="_blank" rel="noopener noreferrer">Audit Log Filter Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/8.4/write-filter-definitions.html" target="_blank" rel="noopener noreferrer">Write audit_log_filter definitons&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/8.4/audit-log-filter-variables.html#audit-log-filter-functions" target="_blank" rel="noopener noreferrer">Audit log filter functions, options, and variables&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="special-thanks">Special Thanks&lt;/h3>
&lt;p>&lt;strong>Yura Sorokin&lt;/strong> for the collaboration that made this blog post possible.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Audit Log</category><category>filter</category><category>component</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>PXC</category><media:thumbnail url="https://percona.community/blog/2025/10/audit-log-filters_hu_a18f51ff6cb4cc34.jpg"/><media:content url="https://percona.community/blog/2025/10/audit-log-filters_hu_c2edeb7c9bc7446a.jpg" medium="image"/></item><item><title>Percona at PostgreSQL Conference Europe 2025</title><link>https://percona.community/blog/2025/10/03/percona-at-postgresql-conference-europe-2025/</link><guid>https://percona.community/blog/2025/10/03/percona-at-postgresql-conference-europe-2025/</guid><pubDate>Fri, 03 Oct 2025 11:00:00 UTC</pubDate><description>We’re proud to announce that Percona is a Platinum Sponsor of PostgreSQL Conference Europe (PGConf.EU) 2025, taking place October 21–24, 2025 in Riga, Latvia 🇱🇻 at the Radisson Blu Latvija Conference Center.</description><content:encoded>&lt;p>We’re proud to announce that Percona is a Platinum Sponsor of &lt;a href="https://2025.pgconf.eu/" target="_blank" rel="noopener noreferrer">PostgreSQL Conference Europe (PGConf.EU) 2025&lt;/a>, taking place October 21–24, 2025 in Riga, Latvia 🇱🇻 at the Radisson Blu Latvija Conference Center.&lt;/p>
&lt;p>As a Platinum Sponsor, you can find Percona in a prime main exhibit floor location; a chance to connect directly with our PostgreSQL experts from around the world. Visitors stopping by our booth will be able to:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Enter raffles and win Percona &amp; PostgreSQL SWAG&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Watch live demos and learn from expert-led sessions&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Explore open source PostgreSQL solutions built for scalability, performance, and security&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1959" height="1321" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/10/intro_hu_281a9aa0e40d450b.webp 768w, https://percona.community/blog/2025/10/intro_hu_4e022c6e23e54fc6.webp 480w, https://percona.community/blog/2025/10/intro_hu_6292392cfe47c127.webp 1400w" src="https://percona.community/blog/2025/10/intro_hu_6292392cfe47c127.webp" alt="All Things Open 2021" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As a leader in open source database management and services, Percona supports innovation, collaboration, and the sharing of knowledge that drives the open source ecosystem forward. By &lt;a href="https://2025.pgconf.nyc/" target="_blank" rel="noopener noreferrer">participating in PostgreSQL Conference Europe&lt;/a>, Percona connects with developers, contributors, and industry leaders to discuss the latest trends, challenges, and advancements. We look forward to further showcasing Percona’s dedication to empowering organizations with robust, scalable, and secure open source database solutions.&lt;/p>
&lt;h2 id="about-the-event">About the event&lt;/h2>
&lt;p>This year’s conference is the 15th Annual PostgreSQL Conference Europe. The conference is organised by &lt;strong>PostgreSQL Europe&lt;/strong>, with participation from most of the &lt;strong>PostgreSQL user groups around Europe&lt;/strong>, and is intended to be an important meeting and cooperation point for users both in and out of Europe.
PGConf.EU is a unique chance for European PostgreSQL users and developers to catch up, learn, build relationships, get to know each other and consolidate a real network of professionals that use and work with PostgreSQL.&lt;/p>
&lt;h2 id="percona-speaker-agenda">Percona Speaker Agenda&lt;/h2>
&lt;p>Our experts are taking the stage across multiple tracks to share insights, hands-on experience, and forward-looking innovations:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="959" height="882" sizes="(max-width: 959px) 100vw, 959px" srcset="https://percona.community/blog/2025/10/all_hu_85d4fffa59baa5af.webp 768w, https://percona.community/blog/2025/10/all_hu_bc52b3308f31d676.webp 480w, https://percona.community/blog/2025/10/all_hu_ebd2398c7da8ee1c.webp 959w" src="https://percona.community/blog/2025/10/all_hu_ebd2398c7da8ee1c.webp" alt="All Things Open 2021" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="wednesday-october-22">Wednesday, October 22&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7056-what-implementing-pg_tde-taught-us-about-postgresql/" target="_blank" rel="noopener noreferrer">What implementing pg_tde taught us about PostgreSQL&lt;/a>&lt;/strong>&lt;/p>
&lt;p>👤 &lt;strong>Jan Wieremjewicz&lt;/strong>&lt;/p>
&lt;p>⏰ 16:05 – 16:55 | Room: Alfa | Track: Community (45 minutes)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="thursday-october-23">Thursday, October 23&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7134-kubernetes-from-the-database-out/" target="_blank" rel="noopener noreferrer">Kubernetes from the Database Out&lt;/a>&lt;/strong>&lt;/p>
&lt;p>👤 &lt;strong>Alastair Turner&lt;/strong>&lt;/p>
&lt;p>⏰ 10:25 – 10:55 | Room: Omega 2 | Track: DBA (25 minutes)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7191-tde-as-an-extension-a-different-path-for-postgresql-encryption/" target="_blank" rel="noopener noreferrer">TDE as an Extension: A Different Path for PostgreSQL Encryption&lt;/a>&lt;/strong>&lt;/p>
&lt;p>👤 &lt;strong>Zsolt Parragi&lt;/strong>&lt;/p>
&lt;p>⏰ 11:25 – 12:15 | Room: Beta | Track: Sponsors&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7189-why-postgresql-took-the-crown-from-mysql-and-what-lies-ahead/" target="_blank" rel="noopener noreferrer">Why PostgreSQL took the crown from MySQL and what lies ahead&lt;/a>&lt;/strong>&lt;/p>
&lt;p>👤 &lt;strong>Peter Zaitsev&lt;/strong>&lt;/p>
&lt;p>⏰ 17:20 – 17:35 | Room: Omega 1 | Track: Platinum Sponsor Keynotes&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="friday-october-24">Friday, October 24&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7192-lessons-from-two-decades-of-hacking-the-proprietary-value-into-open-source-databases/" target="_blank" rel="noopener noreferrer">Lessons from two decades of hacking the proprietary value into open source databases&lt;/a>&lt;/strong>&lt;/p>
&lt;p>👤 &lt;strong>Michal Nosek&lt;/strong>&lt;/p>
&lt;p>⏰ 09:25 – 10:15 | Room: Beta | Track: Sponsors&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>See the full agenda &lt;a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h2 id="community-leadership--kai-wagner">Community Leadership – Kai Wagner&lt;/h2>
&lt;p>We are also proud that &lt;a href="https://www.linkedin.com/in/kai-wagner-b1b661152/" target="_blank" rel="noopener noreferrer">Kai Wagner&lt;/a>, Senior Engineering Manager for PostgreSQL at Percona, is serving on the PGConf.EU 2025 Selection Committee.
Kai is a long-time open source contributor and speaker, actively involved in projects like Ceph, openATTIC, and PostgreSQL. As a community builder and PGConf Germany organizer, he helps shape the PostgreSQL ecosystem and ensure diverse, impactful content is represented at the conference.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="767" height="371" sizes="(max-width: 767px) 100vw, 767px" srcset="https://percona.community/blog/2025/10/kai-wagner_hu_57c882c80c54630d.webp 480w, https://percona.community/blog/2025/10/kai-wagner_hu_d428cdbeb4146600.webp 767w" src="https://percona.community/blog/2025/10/kai-wagner_hu_d428cdbeb4146600.webp" alt="All Things Open 2021" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Join us in Riga this October to connect, learn, and celebrate PostgreSQL with Percona and the wider open source community!&lt;/p></content:encoded><author>Edith Puclla</author><author>Jan Wieremjewicz</author><category>sponsorship</category><category>opensource</category><category>Event</category><media:thumbnail url="https://percona.community/blog/2025/10/intro_hu_c5ccf24ef223a13c.jpg"/><media:content url="https://percona.community/blog/2025/10/intro_hu_ba91e7415034f8f0.jpg" medium="image"/></item><item><title>Audit Log Filter Component</title><link>https://percona.community/blog/2025/09/18/audit-log-filter-component/</link><guid>https://percona.community/blog/2025/09/18/audit-log-filter-component/</guid><pubDate>Thu, 18 Sep 2025 00:00:00 UTC</pubDate><description>The audit log filter component in MySQL 8.4 provides administrators with a powerful mechanism for auditing database activity at a fine-grained level. While it offers significant flexibility—such as selectively logging events based on users, hosts, or event types—it can also be challenging to understand and configure correctly.</description><content:encoded>&lt;p>The audit log filter component in MySQL 8.4 provides administrators with a powerful mechanism for auditing database activity at a fine-grained level. While it offers significant flexibility—such as selectively logging events based on users, hosts, or event types—it can also be challenging to understand and configure correctly.&lt;/p>
&lt;p>In this article, we will examine how the audit log filter component works, walk through its core concepts, and share practical tips for configuring and managing audit filters effectively. Our goal is to help you leverage this feature to improve observability, meet compliance requirements, and reduce unnecessary logging overhead.&lt;/p>
&lt;h3 id="enabling-audit-log-filter">Enabling Audit Log Filter&lt;/h3>
&lt;p>We will be using Percona Server 8.4.4 or higher in the examples below. First, we need to enable the audit log filter component. To install the audit log filter component, we need to run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql -u root -p &lt; /usr/share/percona-server/mysql/share/audit_log_filter_linux_install.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify that the audit log filter component is enabled by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">select * from mysql.component;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------------+-----------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| component_id | component_group_id | component_urn |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------------+-----------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 2 | 1 | file://component_audit_log_filter |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------------+-----------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Installing the component creates two new tables in the mysql system database: audit_log_filter and audit_log_user. These tables store the audit log filter definitions and the user-to-filter mappings. Together, they are referred to as the audit log filter tables.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Tables_in_mysql |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| audit_log_filter |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| audit_log_user |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------------------------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Although the configuration is persisted in these tables, they are not usually modified directly with INSERT or UPDATE statements. Instead, MySQL provides built-in functions such as:&lt;/p>
&lt;ul>
&lt;li>audit_log_filter_set_filter()&lt;/li>
&lt;li>audit_log_filter_set_user()&lt;/li>
&lt;/ul>
&lt;p>To manage filter definitions and user assignments safely.&lt;/p>
&lt;p>Configure the my.cnf file to define the desired audit log output format and specify the location of the audit.log file. In the example below, the log format is set to JSON, but other formats (e.g., NEW or OLD) can also be configured depending on your requirements. The audit log file can be written to any path accessible to the MySQL server process.&lt;/p>
&lt;p>Example Changes:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[mysqld]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># auditlog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">audit_log_filter.format=JSON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">audit_log_filter.file=/var/lib/mysql/audit.log&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Restart mysql server to apply the changes:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo systemctl restart mysqld&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The audit log filter install is complete. Now we can start using the audit log filter component.&lt;/p>
&lt;h3 id="creating-audit-log-filters">Creating Audit Log Filters&lt;/h3>
&lt;p>The audit log filter component in MySQL 8.4 provides fine-grained control over database auditing. Instead of logging all events indiscriminately, administrators can define audit log filters, which are rule sets that determine exactly which events should be captured and which should be excluded.&lt;/p>
&lt;p>This allows you to:&lt;/p>
&lt;ul>
&lt;li>Log only the activity relevant to security, compliance, or troubleshooting.&lt;/li>
&lt;li>Reduce unnecessary noise and audit log volume.&lt;/li>
&lt;li>Apply different filters to specific users, hosts, or accounts for tailored auditing.&lt;/li>
&lt;/ul>
&lt;p>Because filters can be customized and assigned at the user or host level, the audit log filter component offers both flexibility and efficiency, making it a powerful mechanism for monitoring and securing database activity while minimizing overhead.&lt;/p>
&lt;p>Lets create a rule that will log all events:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_filter('log_all_events', '{ "filter": {"log": true } }');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lets assign the rule to the user:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_set_user('%', 'log_all_events');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT audit_log_filter_flush();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now all users will have all their events logged.&lt;/p>
&lt;p>Before proceeding, ensure that the jq utility is installed on your system. The installation commands provided in the examples below are compatible with both RHEL-based and Debian-based distributions.&lt;/p>
&lt;p>RHEL builds&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo yum install jq&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Debian builds&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo apt install jq&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To validate that the audit log filter is functioning as expected, we can inspect the raw contents of the audit.log file. Since the log entries are in JSON format, using jq provides an efficient way to query and extract specific events. For example, to filter and display only connection-related events, run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cat audit.log | jq '.[]|select(.class=="connection")'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command streams the log file, parses the JSON structure, and returns only entries where the “class” field is equal to “connection”. This approach allows for targeted analysis, making it easier to verify filter behavior, troubleshoot issues, or monitor specific event classes without manually parsing large volumes of log data.&lt;/p>
&lt;p>Example Output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "timestamp": "2025-07-24 07:18:00",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "id": 27110,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "class": "connection",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "event": "connect",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "connection_id": 415,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "account": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "user": "wayne",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "host": "localhost"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "login": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "user": "wayne",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "os": "",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "ip": "",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "proxy": ""
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "connection_data": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "connection_type": "socket",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "status": 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "db": ""
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "connection_attributes": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "_pid": "717914",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "_platform": "aarch64",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "_os": "Linux",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "_client_name": "libmysql",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "os_user": "wayne",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "_client_version": "8.4.5-5",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "program_name": "mysql"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I’ll cover more advanced filter configurations in a follow-up post—stay tuned for Part 2 of the Audit Log Filter Component series.&lt;/p>
&lt;p>In summary, the audit log filter component in MySQL 8.4 provides administrators with a flexible and fine-grained approach to database auditing. By tailoring filters to specific users, hosts, and event types, you can ensure that only the most relevant activity is logged, making it easier to meet compliance requirements while reducing overhead. With proper configuration and careful use of filters, you can transform the audit log from a noisy data dump into a precise monitoring tool that strengthens both security and observability in your MySQL environment.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Audit Log</category><category>filter</category><category>component</category><category>MySQL</category><category>Community</category><category>Percona Server</category><category>PXC</category><media:thumbnail url="https://percona.community/blog/2025/09/audit-log-filter_hu_a21f707e924f3dd4.jpg"/><media:content url="https://percona.community/blog/2025/09/audit-log-filter_hu_555048615921e7a5.jpg" medium="image"/></item><item><title>Encrypting PostgreSQL Tables with PG_TDE: Step-by-Step Guide for Beginners</title><link>https://percona.community/blog/2025/09/01/encrypting-postgresql-tables-with-pg_tde-step-by-step-guide-for-beginners/</link><guid>https://percona.community/blog/2025/09/01/encrypting-postgresql-tables-with-pg_tde-step-by-step-guide-for-beginners/</guid><pubDate>Mon, 01 Sep 2025 11:00:00 UTC</pubDate><description>Let’s install and try out a new package for PostgreSQL — PG_TDE by Percona. This extension adds Transparent Data Encryption (TDE), a mechanism that allows data to be encrypted at the storage level without affecting application behavior.</description><content:encoded>&lt;p>Let’s install and try out a new package for PostgreSQL — &lt;a href="https://docs.percona.com/pg-tde/index.html" target="_blank" rel="noopener noreferrer">PG_TDE&lt;/a> by Percona. This extension adds Transparent Data Encryption (TDE), a mechanism that allows data to be encrypted at the storage level without affecting application behavior.&lt;/p>
&lt;p>TDE protects data in case of disk, dump, or backup compromise: everything stored at the file system level is encrypted. Meanwhile, the application continues to work with the data as usual — encryption and decryption happen transparently, without changes to SQL queries or logic.&lt;/p>
&lt;p>In this post, we’ll deploy PostgreSQL with PG_TDE in Docker, configure keys, create an encrypted table, and verify that the data is truly protected — even if someone gains direct access to the files. Other installation methods are available in &lt;a href="https://docs.percona.com/postgresql/" target="_blank" rel="noopener noreferrer">the documentation&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>For simplicity and speed of testing, we’ll use a key provider with a keyring file (for dev/test only) — the easiest way to get started with PG_TDE. This approach is convenient for local development and demos, as it doesn’t require additional dependencies or infrastructure.&lt;/p>&lt;/blockquote>
&lt;h2 id="installing-postgresql-with-pg_tde-via-docker">Installing PostgreSQL with PG_TDE via Docker&lt;/h2>
&lt;p>PG_TDE is part of the &lt;a href="https://docs.percona.com/postgresql/" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> — an enhanced PostgreSQL distribution that includes tools for monitoring, auditing, replication, and of course, Transparent Data Encryption. We’ll use the official &lt;a href="https://docs.percona.com/postgresql/17/docker.html" target="_blank" rel="noopener noreferrer">Docker image&lt;/a>, which already contains everything needed to run PG_TDE.&lt;/p>
&lt;p>Let’s start by launching the container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker run --name pg-tde \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -e POSTGRES_PASSWORD=secret \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -e ENABLE_PG_TDE=1 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -p 5432:5432 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -d percona/percona-distribution-postgresql:17.5-3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>POSTGRES_PASSWORD=secret&lt;/code> — sets the superuser password&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ENABLE_PG_TDE=1&lt;/code> — enables Transparent Data Encryption support&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Port 5432 — standard for PostgreSQL&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>After launching, connect to PostgreSQL inside the container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker exec -it pg-tde psql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And activate the PG_TDE extension:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE EXTENSION pg_tde;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify that PG_TDE is enabled and check its version using the SQL function:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT pg_tde_version();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here’s the result at the time of writing — I’m using PG_TDE 2.0&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1100" height="754" sizes="(max-width: 1100px) 100vw, 1100px" srcset="https://percona.community/blog/2025/09/pg_tde-docker-version_hu_96ec52cd4c5cb39b.webp 768w, https://percona.community/blog/2025/09/pg_tde-docker-version_hu_66994dd22f65323c.webp 480w, https://percona.community/blog/2025/09/pg_tde-docker-version_hu_2c305eae3f62d943.webp 1100w" src="https://percona.community/blog/2025/09/pg_tde-docker-version_hu_2c305eae3f62d943.webp" alt="PG_TDE - Docker - Version" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I also connected to PostgreSQL using pgAdmin, accessing via localhost, user postgres, and the password from POSTGRES_PASSWORD.&lt;/p>
&lt;p>pgAdmin is very convenient for exploring the database via UI.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2038" height="1560" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_17d3f96598113a99.webp 768w, https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_58485319f6f811bf.webp 480w, https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_84ee0d91d9f44ed3.webp 1400w" src="https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_84ee0d91d9f44ed3.webp" alt="PG_TDE - Docker - pgAdmin" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now TDE is ready — we can create encrypted tables, manage keys, and verify how data is protected at the storage level.&lt;/p>
&lt;h2 id="configuring-encryption-keys-with-pg_tde">Configuring Encryption Keys with PG_TDE&lt;/h2>
&lt;p>After installing and activating the pg_tde extension, the next step is to configure the keys that will be used for data encryption. In this example, we’re using a key provider based on a keyring file — a simple and fast way to start working with PG_TDE in development mode.&lt;/p>
&lt;blockquote>
&lt;p>Important: The keyring file stores keys in unencrypted form and is intended for testing and development only. For production, it’s recommended to use an external key store such as HashiCorp Vault — we’ll cover that in the next post.&lt;/p>&lt;/blockquote>
&lt;p>Add the key provider:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT pg_tde_add_database_key_provider_file(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'file-vault',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '/tmp/pg_tde_local_keyring.per'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Registers a key provider named ‘file-vault’&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Specifies that keys will be stored in the file located at &lt;code>/tmp/pg_tde_local_keyring.per&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The file path can be anything — you can use a different location or filename, as long as PostgreSQL has access to it. If the file doesn’t exist, it will be created when the first key is generated&lt;/p>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>It’s important not to lose this file — it contains the encryption keys. If needed, it can be placed in a local folder and mounted into the container via Docker, but for this task, using a path inside the container is perfectly fine.&lt;/p>&lt;/blockquote>
&lt;p>Create the key:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT pg_tde_create_key_using_database_key_provider(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'test-db-key',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'file-vault'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’re creating a key named &lt;code>test-db-key&lt;/code> inside the specified key provider. This key will be used to encrypt tables.&lt;/p>
&lt;p>Set the active key:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT pg_tde_set_key_using_database_key_provider(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'test-db-key',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'file-vault'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command sets &lt;code>test-db-key&lt;/code> as the current active key, which will be used when creating new encrypted tables.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1136" height="952" sizes="(max-width: 1136px) 100vw, 1136px" srcset="https://percona.community/blog/2025/09/pg-tde-key-creation_hu_76c057eba77706a1.webp 768w, https://percona.community/blog/2025/09/pg-tde-key-creation_hu_7100bd36f410c15c.webp 480w, https://percona.community/blog/2025/09/pg-tde-key-creation_hu_1a8998c9d534dc0b.webp 1136w" src="https://percona.community/blog/2025/09/pg-tde-key-creation_hu_1a8998c9d534dc0b.webp" alt="PG_TDE - Docker - Key creation" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Let’s verify.&lt;/p>
&lt;p>Retrieve key information using the command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT pg_tde_key_info();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Result:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">postgres=# SELECT pg_tde_key_info();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pg_tde_key_info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">------------------------------------------------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (test-db-key,file-vault,1,"2025-09-04 07:16:04.420629+00")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(1 row)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check the contents of the keyring file — just for experimentation.&lt;/p>
&lt;p>In a separate terminal tab, connect to the container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker exec -it pg-tde bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Use &lt;code>hexdump&lt;/code> to read the file since it contains binary data:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">hexdump -C /tmp/pg_tde_local_keyring.per | head&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This will output the first bytes of the file in hex format, allowing you to confirm that the file is not empty and the key is indeed recorded.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1180" height="420" sizes="(max-width: 1180px) 100vw, 1180px" srcset="https://percona.community/blog/2025/09/pg-tde-key-hexdump_hu_77c2f85cbaacebd1.webp 768w, https://percona.community/blog/2025/09/pg-tde-key-hexdump_hu_ab1945b6566ba3cc.webp 480w, https://percona.community/blog/2025/09/pg-tde-key-hexdump_hu_179b09cefaff3ede.webp 1180w" src="https://percona.community/blog/2025/09/pg-tde-key-hexdump_hu_179b09cefaff3ede.webp" alt="PG_TDE - Docker - Key file" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If the file is empty or unchanged — the key may not have been created or written.&lt;/p>
&lt;h2 id="creating-tables-with-data">Creating Tables with Data&lt;/h2>
&lt;p>Now that the encryption key is set, let’s create two tables: one with encryption enabled, and one regular. This will help visually demonstrate how PG_TDE works and how data storage differs.&lt;/p>
&lt;p>Create the encrypted table by specifying &lt;code>USING tde_heap&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE secure_data (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id SERIAL PRIMARY KEY,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secret TEXT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> created_at DATE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">) USING tde_heap;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add a few rows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">INSERT INTO secure_data (secret, created_at) VALUES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('The launch code is hidden in plain sight.', '2025-09-01'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('Trust no one. The truth is encrypted.', '2025-09-02'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('The treasure lies beneath the third stone by the old oak.', '2025-09-03');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a similar table without encryption:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE plain_data (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id SERIAL PRIMARY KEY,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secret TEXT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> created_at DATE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Insert the same data:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">INSERT INTO plain_data (secret, created_at) VALUES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('The launch code is hidden in plain sight.', '2025-09-01'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('Trust no one. The truth is encrypted.', '2025-09-02'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ('The treasure lies beneath the third stone by the old oak.', '2025-09-03');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now both tables contain identical rows, but one stores the data in encrypted form.&lt;/p>
&lt;p>Let’s check the encryption status using PG_TDE.&lt;/p>
&lt;p>Use the built-in function &lt;code>pg_tde_is_encrypted&lt;/code>, which returns true (&lt;code>t&lt;/code>) if the table uses encryption, and false (&lt;code>f&lt;/code>) if not:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">postgres=# SELECT pg_tde_is_encrypted('secure_data');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pg_tde_is_encrypted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(1 row)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">postgres=# SELECT pg_tde_is_encrypted('plain_data');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pg_tde_is_encrypted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(1 row)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Excellent, now we can move on to comparison: let’s see how the data looks inside PostgreSQL — and how it’s stored on disk. This will help confirm that PG_TDE truly protects content, even if someone gains direct access to the files.&lt;/p>
&lt;h2 id="comparing-tables-encrypted-vs-unencrypted">Comparing Tables: Encrypted vs Unencrypted&lt;/h2>
&lt;p>At the SQL query level, both tables — secure_data (encrypted) and plain_data (unencrypted) — look absolutely identical. This is important: PG_TDE does not affect SQL interface behavior, and the application continues to work with the data as usual.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">postgres=# SELECT * FROM secure_data;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id | secret | created_at
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">----+-----------------------------------------------------------+------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 | The launch code is hidden in plain sight. | 2025-09-01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2 | Trust no one. The truth is encrypted. | 2025-09-02
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3 | The treasure lies beneath the third stone by the old oak. | 2025-09-03
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(3 rows)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">postgres=# SELECT * FROM plain_data;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id | secret | created_at
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">----+-----------------------------------------------------------+------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 | The launch code is hidden in plain sight. | 2025-09-01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2 | Trust no one. The truth is encrypted. | 2025-09-02
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3 | The treasure lies beneath the third stone by the old oak. | 2025-09-03
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(3 rows)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="display-in-the-application-using-pgadmin">Display in the application using pgAdmin&lt;/h3>
&lt;p>Data from the &lt;code>plain_data&lt;/code> table:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1452" height="870" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_8e83d61be339ad38.webp 768w, https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_afc238d3a7fd9c36.webp 480w, https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_bbd122e07ad610ba.webp 1400w" src="https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_bbd122e07ad610ba.webp" alt="PG_TDE - Docker - Data from the unencrypted table plain_data" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Data from the encrypted table secure_data:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1372" height="868" sizes="(max-width: 1372px) 100vw, 1372px" srcset="https://percona.community/blog/2025/09/pg-tde-pgadmin-secure_hu_aee7852ab3c5e236.webp 768w, https://percona.community/blog/2025/09/pg-tde-pgadmin-secure_hu_96967344f35333ad.webp 480w, https://percona.community/blog/2025/09/pg-tde-pgadmin-secure_hu_e04cc415dbd22982.webp 1372w" src="https://percona.community/blog/2025/09/pg-tde-pgadmin-secure_hu_e04cc415dbd22982.webp" alt="PG_TDE - Docker - Data from the unencrypted table plain_data" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The pgAdmin screenshots also show no differences: data is displayed in readable form, regardless of whether the table is encrypted. This confirms that PG_TDE operates at the storage level, without affecting data handling logic.&lt;/p>
&lt;p>As you can see, the result is identical: encryption is transparent to the user and application.&lt;/p>
&lt;h2 id="comparing-table-file-contents">Comparing Table File Contents&lt;/h2>
&lt;p>Now let’s verify that PG_TDE truly encrypts data at the storage level. To do this, we’ll locate the physical table files and compare their contents using hexdump.&lt;/p>
&lt;p>In PostgreSQL, each table is physically stored as one or more files in the base directory, inside the folder corresponding to the database. The file path looks like &lt;code>$PGDATA/base/&lt;database_oid>/&lt;relfilenode>&lt;/code>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>database_oid&lt;/code> — unique database identifier (OID), retrieved from pg_database&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>relfilenode&lt;/code> — table file identifier, retrieved from pg_class&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="step-1-retrieve-identifiers">Step 1: Retrieve Identifiers&lt;/h3>
&lt;p>Get the OID of the current database:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">postgres=# SELECT oid, datname FROM pg_database WHERE datname = current_database();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> oid | datname
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-----+----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 5 | postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(1 row)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Get the relfilenode of the tables:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">postgres=# SELECT relname, relfilenode FROM pg_class WHERE relname IN ('secure_data', 'plain_data');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> relname | relfilenode
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-------------+-------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secure_data | 16433
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> plain_data | 16442
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(2 rows)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, knowing oid = 5 and the relfilenode of the tables, we can locate the table files at:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>data/db/base/5/16433&lt;/code> — encrypted table secure_data&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>data/db/base/5/16442&lt;/code> — unencrypted table plain_data&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="step-2-read-table-files">Step 2: Read Table Files&lt;/h3>
&lt;p>Open a terminal and connect to the PostgreSQL container for bash commands:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker exec -it pg-tde bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Read data from the unencrypted table (plain_data):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">hexdump -C data/db/base/5/16442 | head -n 20&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The output shows that the data is readable directly from the file:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1656" height="732" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/pg-tde-files-plain_hu_ec376dffcc9c1bf5.webp 768w, https://percona.community/blog/2025/09/pg-tde-files-plain_hu_1d9ec087e8bd9433.webp 480w, https://percona.community/blog/2025/09/pg-tde-files-plain_hu_d60ca99571afb3cc.webp 1400w" src="https://percona.community/blog/2025/09/pg-tde-files-plain_hu_d60ca99571afb3cc.webp" alt="PG_TDE - Docker - the data is readable directly from the file" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Read data from the encrypted table (secure_data):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">hexdump -C data/db/base/5/16433 | head -n 20&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The output shows that the contents are fully encrypted:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1864" height="738" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/pg-tde-files-secure_hu_e2ce9319a669121d.webp 768w, https://percona.community/blog/2025/09/pg-tde-files-secure_hu_c4514b6425ccdeb1.webp 480w, https://percona.community/blog/2025/09/pg-tde-files-secure_hu_c006d39c7e421bc3.webp 1400w" src="https://percona.community/blog/2025/09/pg-tde-files-secure_hu_c006d39c7e421bc3.webp" alt="PG_TDE - Docker - the contents are fully encrypted" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>No readable strings — PG_TDE reliably encrypts data at the file system level.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this post, we installed PostgreSQL with Transparent Data Encryption (&lt;a href="https://docs.percona.com/pg-tde/index.html" target="_blank" rel="noopener noreferrer">PG_TDE&lt;/a>) by Percona, created keys, encrypted a table, and verified that the data is truly protected during physical storage. PG_TDE by Percona provides transparent encryption: the application continues to work with the data as usual, while security is enforced at the file system level.&lt;/p>
&lt;p>If you’re working with PostgreSQL and looking to strengthen your data protection, PG_TDE offers a great starting point. I invite you to experiment:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Create encrypted tables and perform a backup. Try restoring it on a different installation and see how key access is enforced.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Explore key rotation, switching the active key, or integrating alternative key providers.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Test how PG_TDE behaves in real-world scenarios: migration, failover, CI/CD pipelines&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>If you’ve already tried PG_TDE or plan to — share your experience and insights by posting in &lt;a href="https://percona.community/blog/2022/02/10/how-to-publish-blog-post/" target="_blank" rel="noopener noreferrer">this blog&lt;/a> or on &lt;a href="https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82" target="_blank" rel="noopener noreferrer">the forum&lt;/a>&lt;/p>
&lt;p>In the next post, we’ll explore a more robust option — integrating PG_TDE with HashiCorp Vault, which enables centralized key management, rotation, and access control.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>Opensource</category><category>PG_TDE</category><category>Security</category><media:thumbnail url="https://percona.community/blog/2025/09/TDE-1_hu_454fa02918023cdc.jpg"/><media:content url="https://percona.community/blog/2025/09/TDE-1_hu_f464c9850d7401bf.jpg" medium="image"/></item><item><title>pg_tde can now encrypt your WAL on PROD!</title><link>https://percona.community/blog/2025/09/01/pg_tde-can-now-encrypt-your-wal-on-prod/</link><guid>https://percona.community/blog/2025/09/01/pg_tde-can-now-encrypt-your-wal-on-prod/</guid><pubDate>Mon, 01 Sep 2025 11:00:00 UTC</pubDate><description>Just recently, we announced the production-ready release of pg_tde, bringing open source Transparent Data Encryption (TDE) to PostgreSQL. Now, I may have spoiled the fun a little with the title, but take a look at the word puzzle below—can you guess the announcement? Bear with me… and my sense of humor, which might be a bit too dry for some :)</description><content:encoded>&lt;p>Just recently, &lt;a href="https://www.percona.com/blog/the-pg_tde-extension-is-now-ready-for-production/" target="_blank" rel="noopener noreferrer">we announced the production-ready release of pg_tde&lt;/a>, bringing open source Transparent Data Encryption (TDE) to PostgreSQL.&lt;/br>
Now, I may have spoiled the fun a little with the title, but take a look at the word puzzle below—can you guess the announcement? Bear with me… and my sense of humor, which might be a bit too dry for some :)&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1536" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_de1b85c59f7f317f.webp 768w, https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_236288ac9cce4268.webp 480w, https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_2c7011e96b66c27f.webp 1400w" src="https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_2c7011e96b66c27f.webp" alt="pg_tde can now encrypt your WAL on PROD!" loading="lazy" />&lt;/figure>&lt;/p>
&lt;figcaption>Yes, it’s an elephant carrying logs to a safe. Because Write Ahead Log (WAL) deserves secure storage!&lt;/figcaption>
&lt;p>At the time of the TDE GA release, one of the most common questions was:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>“How does pg_tde handle the data in Write Ahead Logs (WAL)?”&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>The short answer back then was:&lt;/p>
&lt;blockquote>
&lt;p>“It &lt;em>can&lt;/em> encrypt WAL data, but the feature is still in beta.”&lt;/p>&lt;/blockquote>
&lt;p>I’m happy to share that this capability is now production ready as well! WAL encryption has reached General Availability (GA) and is included in the latest release of &lt;a href="https://docs.percona.com/postgresql/17/release-notes/release-notes-v17.5.3.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 17.5.3&lt;/a>.&lt;/p>
&lt;h3 id="how-to-use-it">How to use it?&lt;/h3>
&lt;p>To use WAL encryption, you’ll need both:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/pg-tde/index/supported-versions.html" target="_blank" rel="noopener noreferrer">pg_tde extension&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/postgresql/17/" target="_blank" rel="noopener noreferrer">Percona Server for PostgreSQL&lt;/a> (included in Percona Distribution)&lt;/li>
&lt;/ul>
&lt;p>Why? Because &lt;a href="https://docs.percona.com/pg-tde/index.html" target="_blank" rel="noopener noreferrer">pg_tde&lt;/a> relies on patches that extend PostgreSQL APIs, and these are not yet available in the upstream PostgreSQL Community releases. Our long-term goal is to ensure they are available in the Community PostgreSQL Server as well, but for now you’ll find them only in Percona Server.
If you’ve already been running the GA version of TDE in &lt;a href="https://docs.percona.com/postgresql/17/release-notes-v17.5.2.html" target="_blank" rel="noopener noreferrer">Percona Distribution 17.5.2&lt;/a>, the upgrade is straightforward. Assuming pg_tde is installed and initialized, &lt;a href="https://docs.percona.com/pg-tde/wal-encryption.html" target="_blank" rel="noopener noreferrer">enabling WAL encryption requires&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>Configure the encryption key by:
&lt;ul>
&lt;li>Setting up the key provider for WAL encryption&lt;/li>
&lt;li>Creating the principal key&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Turning the WAL encryption on by running:
&lt;ul>
&lt;li>ALTER SYSTEM SET pg_tde.wal_encrypt = on;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Restarting the server to apply the changes&lt;/li>
&lt;/ul>
&lt;p>⠀&lt;a href="https://docs.percona.com/postgresql/17/wal-encryption.html#configure-wal-encryption" target="_blank" rel="noopener noreferrer">Full step-by-step documentation is here.&lt;/a>&lt;/p>
&lt;h3 id="some-known-limitations-of-the-first-version">Some known limitations of the first version&lt;/h3>
&lt;p>While WAL encryption is now GA, there are some first version limitations to be aware of:&lt;/p>
&lt;ul>
&lt;li>Compatibility with other extensions: pg_tde has only been validated with the extensions shipped in Percona Distribution. Extensions that rely heavily on WAL (such as some backup, replication, or logical decoding tools) may not yet work correctly.&lt;/li>
&lt;li>Backup and restore:
&lt;ul>
&lt;li>WAL encryption has been tested and verified with PgBackRest, our recommended tool for backup and recovery.&lt;/li>
&lt;li>pg_basebackup works with WAL encryption when using streaming or none methods. While the &lt;code>-x fetch&lt;/code> method is not yet supported for other methods there are some good practices we highly recommend.&lt;/li>
&lt;li>Other backup tools may not yet support WAL encryption. An example is Barman as the following Barman backup methods are not yet supported with WAL encryption:
&lt;ul>
&lt;li>Postgres method&lt;/li>
&lt;li>Rsync method&lt;/li>
&lt;li>Snapshot method&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Native logical replication: tools like pg_createsubscriber are not yet supported with encrypted WAL. We are planning to enable them in one of the next releases. Do share feedback if you need them, it will help us prioritize.&lt;/li>
&lt;/ul>
&lt;p>See more details regarding the WAL encryption GA supported scope in our &lt;a href="https://docs.percona.com/pg-tde/index/tde-limitations.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.
We’ll continue testing compatibility with widely used extensions and documenting what works out of the box versus what requires extra steps. Here, we rely on our community of users as well to let us help prioritize work. Let us know what would you want to see tested or you’ve already tested and need help in making work with WAL encryption.&lt;/p>
&lt;h3 id="why-this-matters-for-regulated-environments">Why this matters for regulated environments&lt;/h3>
&lt;p>STIG documentation proves that PostgreSQL is ready to tackle any government use cases already.
Encryption of both data files and WAL is often a requirement when organizations work under strict compliance frameworks such as:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FIPS 140-2 / 140-3&lt;/strong> – cryptographic module validation requirements for U.S. government use.&lt;/li>
&lt;li>&lt;strong>HIPAA&lt;/strong> – protection of electronic protected health information (ePHI).&lt;/li>
&lt;li>&lt;strong>PCI-DSS&lt;/strong> – encryption requirements for cardholder data at rest.&lt;/li>
&lt;li>&lt;strong>FedRAMP&lt;/strong> and other government security standards – full coverage of data at rest, including temporary and log files.&lt;/li>
&lt;/ul>
&lt;p>Many PostgreSQL distributions rely on filesystem-level encryption or external tooling to meet these standards. With PCI-DSS 4.0 this is no longer enough.
With pg_tde, encryption is native to PostgreSQL internals, ****covering both tables and WAL, implemented with widely recognized cryptographic algorithms via OpenSSL, and integrated with enterprise grade Key Management Systems (KMS). This reduces operational complexity and helps align directly with compliance controls.&lt;/p>
&lt;h3 id="what-does-the-future-hold-for-tde">What does the future hold for TDE?&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="4086" height="4100" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/09/jan-wal_ga1_hu_2ec2e98b664afe08.webp 768w, https://percona.community/blog/2025/09/jan-wal_ga1_hu_4a25d4775a4171da.webp 480w, https://percona.community/blog/2025/09/jan-wal_ga1_hu_fa5ad59969480937.webp 1400w" src="https://percona.community/blog/2025/09/jan-wal_ga1_hu_fa5ad59969480937.webp" alt="pg_tde can now encrypt your WAL on PROD! - 2" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We’re far from done. The roadmap for TDE includes:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>New capabilities:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>More cipher configuration options&lt;/li>
&lt;li>Expanded KMS support (beyond what we support today)&lt;/li>
&lt;li>Temporary files encryption&lt;/li>
&lt;li>Features requested from real-world deployments&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Lifting &lt;a href="https://docs.percona.com/pg-tde/index/tde-limitations.html" target="_blank" rel="noopener noreferrer">current limitations&lt;/a>:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Broader backup/restore scenarios&lt;/li>
&lt;li>Compatibility with logical replication tooling such as pg_createsubscriber&lt;/li>
&lt;li>Validating more third-party extensions&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Upstream contributions:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Our long-term goal is to contribute the necessary server patches into Community PostgreSQL. This way, pg_tde can eventually run on any PostgreSQL release, not just Percona Server for PostgreSQL.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>We’d love your feedback! If you’ve started using pg_tde or are planning to test it (especially with WAL encryption) let us know what works well, what could be improved or fixed, and what you’d like to see next. Your input directly helps us prioritize features, improve compatibility, and shape the future of TDE in PostgreSQL.&lt;/p>
&lt;p>Share your experiences via our &lt;a href="https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde" target="_blank" rel="noopener noreferrer">Community Forums&lt;/a>, alternatively via GitHub &lt;a href="https://github.com/percona/postgres/discussions" target="_blank" rel="noopener noreferrer">Discussions&lt;/a> or &lt;a href="https://github.com/percona/postgres/issues" target="_blank" rel="noopener noreferrer">Issues&lt;/a>!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/09/jan-wal_ga_cover1_hu_ce9181026382a2e1.jpg"/><media:content url="https://percona.community/blog/2025/09/jan-wal_ga_cover1_hu_6c5c007f8773efee.jpg" medium="image"/></item><item><title>pg_stat_monitor Needs You! Join the Feedback Phase</title><link>https://percona.community/blog/2025/08/13/pg_stat_monitor-needs-you-join-the-feedback-phase/</link><guid>https://percona.community/blog/2025/08/13/pg_stat_monitor-needs-you-join-the-feedback-phase/</guid><pubDate>Wed, 13 Aug 2025 00:00:00 UTC</pubDate><description>At Percona, we believe that great open source software is built with the Community, not just for it. As we plan the next iteration of pg_stat_monitor, our advanced PostgreSQL monitoring extension, we’re taking a closer look at the current feature set and how it aligns with real-world usage.</description><content:encoded>&lt;p>At Percona, we believe that great open source software is built &lt;em>with&lt;/em> the Community, not just &lt;em>for&lt;/em> it. As we plan the next iteration of &lt;a href="https://github.com/percona/pg_stat_monitor" target="_blank" rel="noopener noreferrer">pg_stat_monitor&lt;/a>, our advanced PostgreSQL monitoring extension, we’re taking a closer look at the current feature set and how it aligns with real-world usage.&lt;/p>
&lt;p>In open source, the community isn’t just a user base, it’s the most important stakeholder. While we set the vision, your feedback is the compass that guides us. Your experiences, bug reports, and feature requests are what validate our direction and keep us focused on what matters most. Without your active involvement, it’s impossible to build a tool that truly solves the problems you face every day. Your input ensures pg_stat_monitor evolves in a way that is both innovative and genuinely useful.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1536" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2025/08/jan_feedback_wanted1_hu_2396d2f8e72c7e43.webp 768w, https://percona.community/blog/2025/08/jan_feedback_wanted1_hu_1b88a65128e08f84.webp 480w, https://percona.community/blog/2025/08/jan_feedback_wanted1_hu_b0a298e5150b1617.webp 1024w" src="https://percona.community/blog/2025/08/jan_feedback_wanted1_hu_b0a298e5150b1617.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="your-feedback-is-the-compass">Your Feedback Is the Compass&lt;/h2>
&lt;p>Over time, &lt;a href="https://docs.percona.com/pg-stat-monitor/" target="_blank" rel="noopener noreferrer">pg_stat_monitor&lt;/a> has grown beyond &lt;a href="https://www.percona.com/blog/understand-your-postgresql-workloads-better-with-pg_stat_monitor/" target="_blank" rel="noopener noreferrer">its initial query performance monitoring scope&lt;/a>.&lt;/p>
&lt;p>While many features have proven to be extremely useful (especially in use with &lt;a href="https://docs.percona.com/percona-monitoring-and-management/2/setting-up/client/postgresql.html#pg_stat_monitor" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM&lt;/a>)), others may see limited adoption or give no evidence of any adoption at all. To ensure we’re investing in what really matters, we want to understand what you, the users and contributors, actually rely on day-to-day.&lt;/p>
&lt;h2 id="thats-why-were-kicking-off-a-community-feedback-phase">That’s why we’re kicking off a Community feedback phase&lt;/h2>
&lt;p>We’re especially interested in:&lt;/p>
&lt;ul>
&lt;li>What features of pg_stat_monitor are critical for your workflows?&lt;/li>
&lt;li>Are you using it together with PMM, CLI or in another way?&lt;/li>
&lt;li>Are there parts of the extension that feel unnecessary or unclear?&lt;/li>
&lt;li>What would you love to see in the next release?&lt;/li>
&lt;/ul>
&lt;p>This is just the beginning of a broader review and improvement effort, one that we want to run transparently and inclusively, true to our open source values.&lt;/p>
&lt;p>Whether you’re a developer, DBA, or platform engineer using pg_stat_monitor directly or via tools like PMM, know that your input matters.&lt;/p>
&lt;p>👉 Leave a comment below, or reach out on our &lt;a href="https://forums.percona.com/c/postgresql/pg-stat-monitor/69" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a> or via &lt;a href="https://github.com/percona/pg_stat_monitor/issues" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.
Let’s shape the future of pg_stat_monitor together.&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><category>pg_stat_monitor</category><category>monitoring</category><media:thumbnail url="https://percona.community/blog/2025/08/jan-pgsm-cover1_hu_f25f3c6bafbe318c.jpg"/><media:content url="https://percona.community/blog/2025/08/jan-pgsm-cover1_hu_6eb770cc154f372a.jpg" medium="image"/></item><item><title>Percona pg_tde: A Security Review Reveals Robust Encryption</title><link>https://percona.community/blog/2025/07/24/percona-pg_tde-a-security-review-reveals-robust-encryption/</link><guid>https://percona.community/blog/2025/07/24/percona-pg_tde-a-security-review-reveals-robust-encryption/</guid><pubDate>Thu, 24 Jul 2025 00:00:00 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="320" height="320" sizes="(max-width: 320px) 100vw, 320px" srcset="https://percona.community/blog/2025/07/pg-tde-security-audit-small_hu_bf5b6d6a1f201098.webp 320w" src="https://percona.community/blog/2025/07/pg-tde-security-audit-small_hu_bf5b6d6a1f201098.webp" alt="Percona pg_tde: A Security Review Reveals Robust Encryption" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>At Percona, we are committed to providing robust and secure database solutions. We recently engaged &lt;a href="https://longterm.io/" target="_blank" rel="noopener noreferrer">Longterm Security&lt;/a> for an in-depth review of our Transparent Data Encryption (TDE) feature for PostgreSQL, known as &lt;strong>pg_tde&lt;/strong>. This comprehensive assessment included a cryptographic design evaluation, an application security review for coding errors, and extensive fuzz testing. We’re excited to share the key takeaways from this engagement, highlighting both pg_tde’s strengths and areas for continued improvement.&lt;/p>
&lt;hr>
&lt;h2 id="strong-foundations-in-compliance">Strong Foundations in Compliance&lt;/h2>
&lt;p>A major highlight of the review is pg_tde’s excellent alignment with leading &lt;strong>compliance requirements&lt;/strong>. Our TDE implementation is well-suited to meet the stringent demands of standards like &lt;strong>PCI-DSS, HIPAA, GDPR, and SOC2&lt;/strong>. This is largely due to its adherence to best practices outlined by ECRYPT-CSA, BSI, and NIST, particularly concerning key reuse, master key management, initialization vectors, randomness generation, and various counter modes (CBC, CTR, GCM).&lt;/p>
&lt;p>The sole exception identified for full compliance is CNSA (formerly NSA Suite B), which specifically mandates a 256-bit key size for AES. While pg_tde currently supports AES-128, which is widely considered future-proof and meets most compliance needs for data lifetimes beyond 10 years, adding &lt;strong>AES-256 support remains a key recommendation&lt;/strong> to meet the demands of government and high-security environments.&lt;/p>
&lt;p>While technical work continues on managing temporary files and potentially swappable memory, it’s important to note that compliance standards—such as PCI DSS—generally permit the use of temporary files for encrypting sensitive data like PAN. That said, we understand the practical risks involved and are actively working to mitigate them.&lt;/p>
&lt;hr>
&lt;h2 id="no-major-vulnerabilities-uncovered">No Major Vulnerabilities Uncovered&lt;/h2>
&lt;p>After a comprehensive security engagement, we are pleased to report that &lt;strong>no major vulnerabilities have been found within pg_tde&lt;/strong>. This outcome is a testament to the robust design principles and meticulous implementation that underpin pg_tde, validating our continuous investment in secure development.&lt;/p>
&lt;p>The highest priority issue they found (&lt;strong>LTS-1: Temporary Files Not Subject to Encryption&lt;/strong>) was actually something we already knew about and being on our roadmap, while we documented ways to lessen the risk. Our team is also actively tackling another known issue (&lt;strong>LTS-3&lt;/strong>) where sensitive data or encryption keys might end up on your disk if your system uses “swap memory.” As of today, we do not recommend using SWAP, but we’re actively looking into making it work as well. The other issues they pointed out (&lt;strong>LTS-4 through LTS-7&lt;/strong>) are more like helpful suggestions for making things even more robust, rather than actual vulnerabilities.&lt;/p>
&lt;p>Here’s a quick overview of the findings:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Issue&lt;/th>
&lt;th>Severity&lt;/th>
&lt;th>Status&lt;/th>
&lt;th>Title&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>LTS-1&lt;/td>
&lt;td>Medium&lt;/td>
&lt;td>Already Known&lt;/td>
&lt;td>Temporary Files Not Subject to Encryption&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-2&lt;/td>
&lt;td>Low&lt;/td>
&lt;td>Reported&lt;/td>
&lt;td>Disable Core Dumps when pg_tde is active&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-3.1&lt;/td>
&lt;td>Low&lt;/td>
&lt;td>Already Known&lt;/td>
&lt;td>palloc memory may be swapped to disk, storing customer data&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-3.2&lt;/td>
&lt;td>Low&lt;/td>
&lt;td>Already Known&lt;/td>
&lt;td>palloc memory may be swapped to disk, storing key material from pg_tde&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-4&lt;/td>
&lt;td>Informational&lt;/td>
&lt;td>Informative&lt;/td>
&lt;td>Privileged postgres users with file read access may disclose encryption keys from memory&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-5&lt;/td>
&lt;td>Informational&lt;/td>
&lt;td>Reported&lt;/td>
&lt;td>Keyring_vault.c: Invalid JSON response can lead to NULL dereference&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-6&lt;/td>
&lt;td>Informational&lt;/td>
&lt;td>Informative&lt;/td>
&lt;td>Consider HKDF with Principal Key Usage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-7&lt;/td>
&lt;td>Informational&lt;/td>
&lt;td>Reported&lt;/td>
&lt;td>Document how to securely deploy keyring authentication credentials to systems without storing to disk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LTS-8&lt;/td>
&lt;td>Informational&lt;/td>
&lt;td>Reported&lt;/td>
&lt;td>AES-GCM-SIV is available in very new OpenSSL versions as an alternative key wrapping algorithm&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="always-improving-whats-next-for-pg_tde-around-security">Always Improving: What’s Next for pg_tde around security?&lt;/h2>
&lt;p>The review also helped us pinpoint a few areas where we can make pg_tde even stronger, especially when it comes to handling memory and exploring advanced encryption techniques:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tackling Swap Memory and Core Dumps:&lt;/strong> We’re working on ways to prevent sensitive data and encryption keys from potentially leaking to your disk through swap memory or “core dumps” (which happen when a program crashes). We recommend using encrypted swap partitions and turning off core dumps when pg_tde is active. We’re also looking into using special memory allocators that keep key material from being swapped out.&lt;/li>
&lt;li>&lt;strong>More Encryption Options:&lt;/strong> To meet even more compliance needs and give you more choices, we’re considering adding support for a wider range of encryption ciphers, including AES-CBC-256, AES-CTR-256, AES-GCM-256, ChaCha20-Poly1305, and AES-XTS-256. The 256-bit AES key sizes, in particular, will help us achieve CNSA compliance.&lt;/li>
&lt;li>&lt;strong>Smarter Key Management (HKDF with Principal Key Usage):&lt;/strong> To boost security even further and protect against theoretical key-reuse attacks, we’re evaluating the use of HKDF (HMAC-based Key Derivation Function). This would add an extra layer of protection by creating unique keys for different situations.&lt;/li>
&lt;li>&lt;strong>Securely Handling Your Keyring Credentials:&lt;/strong> It’s super important that your keyring authentication credentials (like Vault tokens or KMIP keys) are never stored in plain text on your disk. We’re putting together clear guidelines for secure deployment, recommending solutions that keep credentials only in memory or use hardware-backed keystores. We’ll also advise against putting credentials in command-line arguments.&lt;/li>
&lt;li>&lt;strong>Keeping an Eye on New Algorithms (AES-GCM-SIV):&lt;/strong> We’re aware of a newer encryption algorithm called AES-GCM-SIV, which is available in very recent OpenSSL versions. While it’s not yet FIPS-140-3 compliant and might be a bit slower, it offers better protection against certain types of attacks, and we’re definitely keeping it on our radar for future consideration.&lt;/li>
&lt;li>&lt;strong>Preparing for Post-Quantum Cryptography:&lt;/strong> The cryptographic landscape is evolving rapidly, with post-quantum encryption (PQC) algorithms on the horizon. As these new algorithms are expected to be more resource-intensive, pg_tde’s granular table-level encryption will be crucial for efficiently managing performance overheads, ensuring your data remains secure without compromising application responsiveness in a post-quantum world. We are actively monitoring these ciphers’ development and standardization.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="our-commitment-to-security">Our Commitment to Security&lt;/h2>
&lt;p>The security review by Longterm Security has been incredibly valuable. It’s not only confirmed pg_tde’s strong capabilities but also given us a clear roadmap for future development. We’re really proud of how secure pg_tde is, which you can see in its readiness for compliance and the fact that no major vulnerabilities were found. Our ongoing efforts to address the identified issues, even the low-priority ones, show just how committed we are to constantly improving and providing you with the most secure database solutions out there.&lt;/p>
&lt;p>We’re committed to building a secure and reliable data encryption solution, and your feedback plays a vital role in shaping its future. &lt;a href="https://docs.percona.com/pg-tde/index/index.html" target="_blank" rel="noopener noreferrer">&lt;strong>Try it out!&lt;/strong>&lt;/a>&lt;/p>
&lt;p>If you want to work directly with the code/test/build, take a look at the &lt;a href="https://github.com/percona/postgres/tree/TDE_REL_17_STABLE/" target="_blank" rel="noopener noreferrer">GitHub project&lt;/a>. For issues, report them via &lt;a href="https://jira.percona.com/browse/PG" target="_blank" rel="noopener noreferrer">Jira&lt;/a> or go to our &lt;a href="https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82" target="_blank" rel="noopener noreferrer">Forum&lt;/a> to ask questions. For detailed deployment guidelines and best practices, take a look at our &lt;a href="https://docs.percona.com/pg-tde/index/index.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>. We’re looking forward to any feedback and collaboration around making PostgreSQL secure.&lt;/p></content:encoded><author>Kai Wagner</author><category>Percona</category><category>pg_tde</category><category>pg_zsolt</category><category>PostgreSQL</category><category>Encryption</category><category>Security</category><category>Compliance</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2025/07/pg-tde-security-audit_hu_588548b9cb0bd69f.jpg"/><media:content url="https://percona.community/blog/2025/07/pg-tde-security-audit_hu_7c838d8e0274e910.jpg" medium="image"/></item><item><title>GitOps Journey: Part 4 – Observability and Monitoring with Coroot in Kubernetes</title><link>https://percona.community/blog/2025/07/22/gitops-journey-part-4-observability-and-monitoring-with-coroot-in-kubernetes/</link><guid>https://percona.community/blog/2025/07/22/gitops-journey-part-4-observability-and-monitoring-with-coroot-in-kubernetes/</guid><pubDate>Tue, 22 Jul 2025 00:01:00 UTC</pubDate><description>Our PostgreSQL cluster is running, and the demo app is generating traffic — but we have no visibility into the health of the Kubernetes cluster, services, or applications.</description><content:encoded>&lt;p>Our PostgreSQL cluster is running, and the demo app is generating traffic — but we have no visibility into the health of the Kubernetes cluster, services, or applications.&lt;/p>
&lt;p>What happens when disk space runs out? What if the database is under heavy load and needs scaling? What if errors are buried in application logs? How busy are the network and storage layers? What’s the actual cost of the infrastructure?&lt;/p>
&lt;p>This is where &lt;a href="https://coroot.com/" target="_blank" rel="noopener noreferrer">Coroot&lt;/a> comes in.&lt;/p>
&lt;p>Coroot is an open-source observability platform that provides dashboards for profiling, logs, service maps, and resource usage — helping you track system health and diagnose issues quickly.&lt;/p>
&lt;p>We’ll deploy it using &lt;strong>Helm via ArgoCD&lt;/strong>, continuing with our GitOps workflow.&lt;/p>
&lt;p>This is Part 4 in our series. Previously, we:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Set up ArgoCD and a GitHub repository for declarative manifests (&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/">Part 1&lt;/a>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Installed a PostgreSQL cluster using Percona Operator&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Deployed a demo application to simulate traffic and interact with the database&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>All infrastructure is defined declaratively and deployed from the GitHub repository, following GitOps practices.&lt;/p>
&lt;p>So far, we’ve explored cluster scaling, user management, and dynamic configuration — and now it’s time for observability.&lt;/p>
&lt;p>We’ll install Coroot by following the &lt;a href="https://docs.coroot.com/installation/kubernetes/" target="_blank" rel="noopener noreferrer">official documentation&lt;/a> for Kubernetes.&lt;/p>
&lt;p>Steps ahead:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Install the Coroot Operator&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Install the Coroot Community Edition&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Let’s get started.&lt;/p>
&lt;h2 id="project-structure">Project Structure&lt;/h2>
&lt;p>We already have a &lt;code>postgres/&lt;/code> directory for PostgreSQL manifests and an &lt;code>apps/&lt;/code> directory for ArgoCD applications.&lt;/p>
&lt;p>We’ll preserve this layout and add a new &lt;code>coroot/&lt;/code> folder for clarity. You can use a different structure if preferred.&lt;/p>
&lt;h2 id="create-manifest-for-installing-the-coroot-operator">Create Manifest for Installing the Coroot Operator&lt;/h2>
&lt;p>The documentation recommends installing via Helm.&lt;br>
Since we use ArgoCD, we’ll create a manifest that installs via Helm.&lt;/p>
&lt;p>Create file: &lt;code>coroot/operator.yaml&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: argoproj.io/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: coroot-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> project: default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoURL: https://coroot.github.io/helm-charts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> chart: coroot-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> targetRevision: 0.4.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> destination:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: https://kubernetes.default.svc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncPolicy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> automated:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> prune: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selfHeal: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncOptions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - CreateNamespace=true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: I’m using version &lt;code>0.4.2&lt;/code>, which was current at the time of writing.&lt;br>
To check available versions, use &lt;a href="https://github.com/coroot/helm-charts/pkgs/container/charts%2Fcoroot-operator" target="_blank" rel="noopener noreferrer">this GitHub link&lt;/a> or Helm CLI:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">helm repo add coroot https://coroot.github.io/helm-charts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm repo update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm search repo coroot-operator --versions&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="create-manifest-for-installing-coroot-community-edition">Create Manifest for Installing Coroot Community Edition&lt;/h2>
&lt;p>Create file: &lt;code>coroot/coroot.yaml&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: argoproj.io/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> project: default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoURL: https://coroot.github.io/helm-charts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> chart: coroot-ce
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> targetRevision: 0.3.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> helm:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parameters:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: clickhouse.shards
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: "2"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: clickhouse.replicas
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: "2"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: service.type
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: LoadBalancer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> destination:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: https://kubernetes.default.svc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncPolicy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> automated:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> prune: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selfHeal: true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This chart creates a minimal Coroot Custom Resource.&lt;br>
I’ve added &lt;code>service.type: LoadBalancer&lt;/code> to expose a public IP.&lt;/p>
&lt;p>If you don’t use LoadBalancer, you’ll need to forward the Coroot port after installation:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl port-forward -n coroot service/coroot-coroot 8080:8080&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="create-argocd-application-manifest">Create ArgoCD Application Manifest&lt;/h2>
&lt;p>Since we manage our infrastructure via a GitHub repository, we need an ArgoCD Application that tracks changes in the &lt;code>coroot/&lt;/code> folder.&lt;/p>
&lt;p>Create file: &lt;code>apps/argocd-coroot.yaml&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: argoproj.io/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: coroot-sync-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> project: default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoURL: https://github.com/dbazhenov/percona-argocd-pg-coroot.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> targetRevision: main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> path: coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> destination:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: https://kubernetes.default.svc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncPolicy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> automated:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> prune: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selfHeal: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncOptions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - CreateNamespace=true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This lightweight app will monitor the folder and apply updates automatically if a change is detected (e.g. chart version bump).&lt;/p>
&lt;h2 id="define-chart-installation-order">Define Chart Installation Order&lt;/h2>
&lt;p>We have two charts: &lt;code>operator.yaml&lt;/code> and &lt;code>coroot.yaml&lt;/code>, and the operator must be installed first.&lt;/p>
&lt;p>Create &lt;code>coroot/kustomization.yaml&lt;/code> to specify resource order:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: kustomize.config.k8s.io/v1beta1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Kustomization
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">resources:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - operator.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - coroot.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="publish-manifests-to-github">Publish Manifests to GitHub&lt;/h2>
&lt;p>Check which files were changed:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add changes:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify staged files:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Commit:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git commit -m "Installing Coroot Operator and Coroot with ArgoCD"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Push:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git push origin main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="apply-argocd-application">Apply ArgoCD Application&lt;/h2>
&lt;p>Deploy the ArgoCD app that installs Coroot from our GitHub repository:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl apply -f apps/argocd-coroot.yaml -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Validate installation and sync:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3134" height="1830" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_5119d88542e15c2.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_1dc96f34d87ac33a.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_61c535184bb19e2e.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_61c535184bb19e2e.webp" alt="GitOps - ArgoCD and Coroot" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We now see &lt;code>coroot&lt;/code>, &lt;code>coroot-operator&lt;/code>, and &lt;code>coroot-sync-app&lt;/code> deployed.&lt;/p>
&lt;h2 id="access-coroot-ui">Access Coroot UI&lt;/h2>
&lt;p>Since we deployed Coroot using LoadBalancer, retrieve its external IP:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get svc -n coroot&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Open EXTERNAL-IP on port 8080.&lt;br>
For example: &lt;code>http://35.202.140.216:8080/&lt;/code>&lt;/p>
&lt;p>If you didn’t use LoadBalancer, run port-forward:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl port-forward -n coroot service/coroot-coroot 8080:8080&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then visit &lt;code>http://localhost:8080&lt;/code>&lt;/p>
&lt;p>You’ll be prompted to set an admin password on first login.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1568" height="1378" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_98bd4c5814539caf.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_2bdf3296e9a8e328.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_c6363ed11d7aac0a.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_c6363ed11d7aac0a.webp" alt="GitOps - ArgoCD and Coroot" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="exploring-coroot-ui">Exploring Coroot UI&lt;/h2>
&lt;p>On the home page, we see a list of applications running in the cluster and resource usage.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2648" height="934" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_b5384eac8dfdd7ad.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_983912567f2d2a84.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_b76841ac838fa559.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_b76841ac838fa559.webp" alt="GitOps - ArgoCD and Coroot - Home" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I increased the load on the PostgreSQL cluster using the Demo App to test observability.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2332" height="1088" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_1331bfb2fea1a921.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_d3749ba2a2e6232b.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_55ed1b8f8fbc6f.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_55ed1b8f8fbc6f.webp" alt="GitOps - ArgoCD and Coroot - Demo App" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The PostgreSQL cluster dashboard offers several tabs:&lt;/p>
&lt;ul>
&lt;li>CPU&lt;/li>
&lt;li>Memory&lt;/li>
&lt;li>Storage&lt;/li>
&lt;li>Instances&lt;/li>
&lt;li>Logs&lt;/li>
&lt;li>Profiling&lt;/li>
&lt;li>Tracing&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="2648" height="1822" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_43ebdeb78914b123.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_e6979abf8abd738a.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_bdcd89d675ee59f3.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_bdcd89d675ee59f3.webp" alt="GitOps - ArgoCD and Coroot - PG Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Coroot displays a visual map of service interactions — showing which app connects to the PostgreSQL cluster.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2660" height="1842" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_82ea4951003eb885.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_ee1777680571399a.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_f08fb105d9e1ad9.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_f08fb105d9e1ad9.webp" alt="GitOps - ArgoCD and Coroot - PG Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The &lt;strong>Profiling&lt;/strong> tab looks excellent and intuitive. Here’s the Demo App profiling view:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3298" height="1820" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_1556a8ad90732e27.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_349de63b2d120faa.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_c3567f1b081d894e.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_c3567f1b081d894e.webp" alt="GitOps - ArgoCD and Coroot - Demo App Profiling" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I also triggered an intentional error in the demo app.&lt;br>
Coroot correctly displayed it in both the home view and the app details page.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3298" height="1820" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_1556a8ad90732e27.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_349de63b2d120faa.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_c3567f1b081d894e.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_c3567f1b081d894e.webp" alt="GitOps - ArgoCD and Coroot - Demo App Logs" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I especially liked the &lt;strong>Logs&lt;/strong> and &lt;strong>Costs&lt;/strong> sections in the sidebar — very well implemented.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2556" height="1668" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_444eb7183b419f0b.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_7fbd0f5b91891c6.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_8c49aa131e27b1a.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_8c49aa131e27b1a.webp" alt="GitOps - ArgoCD and Coroot - Demo App Logs" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2538" height="1334" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_8110ec809834007e.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_509b43fe7cf7bbf7.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_5c38cb2b84ca1e49.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_5c38cb2b84ca1e49.webp" alt="GitOps - ArgoCD and Coroot - Demo App Costs" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="first-incident-storage-usage-in-postgresql-turns-yellow">First Incident: Storage Usage in PostgreSQL Turns Yellow&lt;/h2>
&lt;p>While exploring Coroot and the cluster, I increased the load on the PostgreSQL cluster using the Demo App.&lt;/p>
&lt;p>After a short while, I noticed that the Postgres disk was full.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2512" height="810" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_c3871d28872dade4.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_6fa285b3441569c.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_2c7ca0e96fefe0ec.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_2c7ca0e96fefe0ec.webp" alt="GitOps - ArgoCD and Coroot - PG Storage" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I opened the cluster details and went to the &lt;strong>Storage&lt;/strong> tab.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2088" height="1834" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_c9ce64d104b1b5d1.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_d546c99e3b20ee2b.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_c2141f879643115a.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_c2141f879643115a.webp" alt="GitOps - ArgoCD and Coroot - PG Storage Details" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>By default, the &lt;code>cr.yaml&lt;/code> file allocates just 1Gi of disk space — which is fine for a test setup.&lt;/p>
&lt;p>Let’s increase disk size the GitOps way.&lt;/p>
&lt;h2 id="increase-storage-size">Increase Storage Size&lt;/h2>
&lt;p>Open the file &lt;code>postgres/cr.yaml&lt;/code> and locate the section:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> dataVolumeClaimSpec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># storageClassName: standard
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> accessModes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ReadWriteOnce
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resources:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> requests:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage: 1Gi&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Change &lt;code>storage&lt;/code> from &lt;code>1Gi&lt;/code> to &lt;code>5Gi&lt;/code>.&lt;/p>
&lt;p>Note: Backup volumes (pgBackRest) are also enabled by default and set to &lt;code>1Gi&lt;/code>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> manual:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoName: repo1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> options:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - --type=full
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># initialDelaySeconds: 120
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repos:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: repo1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> schedules:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> full: "0 0 * * 6"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># differential: "0 1 * * 1-6"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># incremental: "0 1 * * 1-6"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volume:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumeClaimSpec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># storageClassName: standard
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> accessModes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ReadWriteOnce
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resources:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> requests:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage: 1Gi&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Increase this storage to &lt;code>5Gi&lt;/code> as well.&lt;/p>
&lt;p>Save changes to &lt;code>cr.yaml&lt;/code>, then commit and push to the GitHub repository.&lt;/p>
&lt;p>ArgoCD will automatically apply the changes. Pure GitOps magic.&lt;/p>
&lt;p>Check the result in Coroot — everything looks great. The disk is increased to &lt;code>5Gi&lt;/code> and the issue is resolved.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2326" height="1828" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_45b29ea63b878208.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_d1acfb67fbb131b0.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_d7d50fa494825d4f.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_d7d50fa494825d4f.webp" alt="GitOps - ArgoCD and Coroot - PG Storage Details Results" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2706" height="1078" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_e182abd42d52a40e.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_8709492a258fa616.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_b5b0267b6e44597d.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_b5b0267b6e44597d.webp" alt="GitOps - ArgoCD and Coroot - PG Storage Results" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>We’ve installed and tested a solid monitoring tool, and it really makes a difference.&lt;/p>
&lt;p>Across this 4-part series, we walked through the GitOps journey step by step:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/">Part 1&lt;/a> - Created a Kubernetes cluster, installed ArgoCD, and set up a GitHub repository.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/">Part 2&lt;/a> - Deployed a PostgreSQL cluster using Percona Operator for PostgreSQL.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/">Part 3&lt;/a> - Deployed a demo app via ArgoCD using Helm.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Installed and tested &lt;strong>Coroot&lt;/strong>, an excellent open-source observability tool.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Managed the PG cluster through GitHub and ArgoCD — scaled replicas, created users, resized volumes, configured access, and more.&lt;/p>
&lt;p>Thank you for reading — I hope this series was helpful.&lt;/p>
&lt;p>The project files are available in my repository &lt;a href="https://github.com/dbazhenov/percona-argocd-pg-coroot" target="_blank" rel="noopener noreferrer">https://github.com/dbazhenov/percona-argocd-pg-coroot&lt;/a>&lt;/p>
&lt;p>I’d love to hear your questions, feedback, and suggestions for improvement.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>Coroot</category><category>GitOps</category><category>ArgoCD</category><media:thumbnail url="https://percona.community/blog/2025/07/gitops-part-4_hu_2013b3b0cf1fac01.jpg"/><media:content url="https://percona.community/blog/2025/07/gitops-part-4_hu_df439a8a73d324e6.jpg" medium="image"/></item><item><title>GitOps Journey: Part 3 – Deploying a Load Generator and Connecting to PostgreSQL</title><link>https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/</link><guid>https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/</guid><pubDate>Tue, 22 Jul 2025 00:00:50 UTC</pubDate><description>We’ll deploy a demo application into the Kubernetes cluster using ArgoCD to simulate load on the PostgreSQL cluster.</description><content:encoded>&lt;p>We’ll deploy a demo application into the Kubernetes cluster using ArgoCD to simulate load on the PostgreSQL cluster.&lt;/p>
&lt;p>This is a series of articles, in previous parts we:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/">Part 1&lt;/a> - Prepared the environment and installed ArgoCD and GitHub repository.&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/">Part 2&lt;/a> - Installed Percona Operator for Postgres and created a Postgres cluster.&lt;/li>
&lt;/ol>
&lt;p>The application is a custom Go-based service that generates traffic for PostgreSQL, MongoDB, or MySQL.&lt;/p>
&lt;p>It uses a dataset of GitHub repositories and pull requests, and mimics real-world operations like fetching, creating, updating, and deleting records.&lt;br>
Load intensity is configurable through a browser-based control panel.&lt;/p>
&lt;p>We’ll install it using Helm, tracked and deployed via ArgoCD.&lt;/p>
&lt;p>Reference repository: &lt;a href="https://github.com/dbazhenov/github-stat" target="_blank" rel="noopener noreferrer">github-stat&lt;/a>&lt;/p>
&lt;h2 id="create-the-argocd-application-manifest">Create the ArgoCD Application Manifest&lt;/h2>
&lt;p>Create a file named &lt;code>argocd-demo-app.yaml&lt;/code> in the &lt;code>apps/&lt;/code> directory.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: argoproj.io/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: demo-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> project: default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoURL: https://github.com/dbazhenov/github-stat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> targetRevision: main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> path: k8s/helm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> destination:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: https://kubernetes.default.svc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: demo-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncPolicy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> automated:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> prune: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selfHeal: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncOptions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - CreateNamespace=true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This will install the Helm chart from&lt;br>
&lt;code>https://github.com/dbazhenov/github-stat/tree/main/k8s/helm&lt;/code>&lt;/p>
&lt;p>By default, the service is configured as &lt;code>LoadBalancer&lt;/code>, making it accessible from the internet.&lt;/p>
&lt;p>To switch to &lt;code>NodePort&lt;/code> (if needed), override the Helm value:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> helm:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parameters:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: controlPanelService.type
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: NodePort&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’ll keep default settings in this example.&lt;/p>
&lt;h2 id="push-the-application-manifest-to-github">Push the Application Manifest to GitHub&lt;/h2>
&lt;p>Track and commit your changes:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git commit -m "Installing Demo Application in ArgoCD by HELM"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git push origin main &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected Git output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) git status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">On branch main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your branch is up to date with 'origin/main'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Untracked files:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (use "git add &lt;file>..." to include in what will be committed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> apps/argocd-demo-app.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nothing added to commit but untracked files present (use "git add" to track)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git commit -m "Installing Demo Application in ArgoCD by HELM"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[main 03ce175] Installing Demo Application in ArgoCD by HELM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 file changed, 20 insertions(+)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> create mode 100644 apps/argocd-demo-app.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) git push origin main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enumerating objects: 6, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Counting objects: 100% (6/6), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Delta compression using up to 10 threads
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Compressing objects: 100% (4/4), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing objects: 100% (4/4), 686 bytes | 686.00 KiB/s, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To github.com:dbazhenov/percona-argocd-pg-coroot.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 6b2dc98..03ce175 main -> main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="apply-the-argocd-application">Apply the ArgoCD Application&lt;/h2>
&lt;p>Deploy the app via:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl apply -f apps/argocd-demo-app.yaml -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>ArgoCD will install the app and has started tracking the app’s HELM chart&lt;/p>
&lt;h2 id="validate-the-deployment">Validate the Deployment&lt;/h2>
&lt;p>Confirm the app status in ArgoCD UI:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2354" height="1098" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_3fcb32ce211fd0f7.webp 768w, https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_4e2b02a29258bf05.webp 480w, https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_55e7e24f80027161.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_55e7e24f80027161.webp" alt="GitOps - Percona Operator for Postgres and PG Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Check running pods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get pods -n demo-app&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected pods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) kubectl get pods -n demo-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-dataset-6d886f67-j648w 1/1 Running 0 2m52s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-load-577cff97c9-d8j99 1/1 Running 0 2m52s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-valkey-74989c9bf7-gjp4x 1/1 Running 0 2m52s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-web-5b98d4c65c-xmkq9 1/1 Running 0 2m52s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>demo-app-dataset - loads dataset&lt;/li>
&lt;li>demo-app-load - generates traffic&lt;/li>
&lt;li>demo-app-valkey - Redis-compatible DB backend&lt;/li>
&lt;li>demo-app-web - UI dashboard&lt;/li>
&lt;/ul>
&lt;h2 id="open-the-application-dashboard">Open the Application Dashboard&lt;/h2>
&lt;p>Retrieve the external IP:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get svc -n demo-app&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Find the &lt;code>EXTERNAL-IP&lt;/code> of &lt;code>demo-app-web-service&lt;/code>.&lt;/p>
&lt;p>Sample output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) kubectl get svc -n demo-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-valkey-service ClusterIP 34.118.235.203 &lt;none> 6379/TCP 4m59s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">demo-app-web-service LoadBalancer 34.118.232.144 34.28.221.107 80:31308/TCP 4m59s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Access the app in your browser:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2522" height="1552" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_2054bb35bc133344.webp 768w, https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_449300dcd1b710b8.webp 480w, https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_e082313946cd1077.webp 1400w" src="https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_e082313946cd1077.webp" alt="GitOps - ArgoCD Demo App UI" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Navigate to the &lt;strong>Settings&lt;/strong> tab to configure a PostgreSQL connection.&lt;/p>
&lt;h2 id="postgresql-credentials-setup">PostgreSQL Credentials Setup&lt;/h2>
&lt;p>Percona Operator has already (&lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/users.html" target="_blank" rel="noopener noreferrer">Application and system users&lt;/a>):&lt;/p>
&lt;ul>
&lt;li>Created schema and database &lt;code>cluster1&lt;/code>&lt;/li>
&lt;li>Created user &lt;code>cluster1&lt;/code>&lt;/li>
&lt;li>Stored credentials in &lt;code>cluster1-pguser-cluster1&lt;/code> secret&lt;/li>
&lt;/ul>
&lt;p>Extract the password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get secret cluster1-pguser-cluster1 -n postgres-operator --template='{{.data.password | base64decode}}{{"\n"}}'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s connect to the database from the Demo application using the given user and cluster1-pgbouncer.postgres-operator.svc host&lt;/p>
&lt;p>In the Connection String field enter&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">user=cluster1 password='[PASSWORD]' dbname=cluster1 host=cluster1-pgbouncer.postgres-operator.svc port=5432&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2674" height="1840" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_97df594f136c5561.webp 768w, https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_da9f26066f91c411.webp 480w, https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_bdc2bb12fd153f27.webp 1400w" src="https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_bdc2bb12fd153f27.webp" alt="GitOps - ArgoCD Demo App UI - Connect" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The connection has been successfully created, this is good.&lt;/p>
&lt;p>To start generating the load, we need to import the Dataset using the Import Dataset button.&lt;/p>
&lt;h2 id="dataset-import-error-create-schema-denied">Dataset Import Error: Create Schema Denied&lt;/h2>
&lt;p>During import, the app tries to create a schema.&lt;br>
By default, pgBouncer limits user privileges, preventing this action.&lt;/p>
&lt;p>Percona &lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/users.html#superuser-and-pgbouncer" target="_blank" rel="noopener noreferrer">documentation&lt;/a> suggests enabling &lt;code>proxy.pgBouncer.exposeSuperusers&lt;/code> and creating a privileged user.&lt;/p>
&lt;p>We’ll handle this via GitOps. It seems cool that we’ll be doing this with tracking in Git, as these are important settings and we shouldn’t forget about them and turn them off in the future.&lt;/p>
&lt;h2 id="define-a-new-postgresql-user">Define a New PostgreSQL User&lt;/h2>
&lt;p>We will make changes to postgres/cr.yaml that will add a new user and also enable the proxy.pgBouncer.exposeSuperusers option.&lt;/p>
&lt;p>In the postgres/cr.yaml file I found the users section, uncommented and added my user data.&lt;/p>
&lt;p>In &lt;code>postgres/cr.yaml&lt;/code>, add:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> users:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: daniil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> databases:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - demo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> options: "SUPERUSER"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: ASCII
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secretName: "daniil-credentials"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: In production, use scoped permissions like &lt;code>"LOGIN CREATE CREATEDB"&lt;/code> rather than &lt;code>SUPERUSER&lt;/code>.&lt;/p>
&lt;p>I also found the proxy.pgBouncer.exposeSuperusers setting and set it to true&lt;/p>
&lt;p>Update pgBouncer config:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> proxy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pgBouncer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: docker.io/percona/percona-pgbouncer:1.24.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> exposeSuperusers: true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Commit and push:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git commit -m "Postgres cluster: Creating a new user and pgBouncer.exposeSuperusers"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git push origin main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After a couple of minutes, ArgoCD will synchronize the changes and Percona Operator will create the user and change the configuration.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2270" height="892" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_36557a63803d5a63.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_bc27ec6abd07e21b.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_7065bd17247e89e5.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_7065bd17247e89e5.webp" alt="GitOps - ArgoCD Demo App UI" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="connect-with-the-new-user">Connect With the New User&lt;/h2>
&lt;p>Get the password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get secret daniil-credentials -n postgres-operator --template='{{.data.password | base64decode}}{{"\n"}}'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s replace Connection String in Demo application, I got the following string&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">user=daniil password='iKj:e[wT3*g]OF5+f' dbname=dataset host=cluster1-pgbouncer.postgres-operator.svc port=5432&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2706" height="1810" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_4693360115a7ff56.webp 768w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_15494b87c8190811.webp 480w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_8b3f63eb0b03c583.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_8b3f63eb0b03c583.webp" alt="GitOps - ArgoCD Demo App UI - Connection" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Click the “Import Dataset” button and wait a few minutes until the import is in Done status in the Dataset tab.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2600" height="1038" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_96875a5497a661f0.webp 768w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_9f9764d887e17cdf.webp 480w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_e5b77945bc3615ee.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_e5b77945bc3615ee.webp" alt="GitOps - ArgoCD Demo App UI - Connection" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="enable-load-generation">Enable Load Generation&lt;/h2>
&lt;p>Activate the load generator:&lt;/p>
&lt;ul>
&lt;li>Toggle &lt;strong>Enable Load&lt;/strong> in the connection settings&lt;/li>
&lt;li>Click &lt;strong>Update Connection&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1618" height="988" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_2657adb51fe5d6e6.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_e604eb47b1c9a4b0.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_47edc66203d2c9e5.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_47edc66203d2c9e5.webp" alt="GitOps - ArgoCD Demo App UI - Enable Load" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Open the &lt;strong>Load Generator Control Panel&lt;/strong> and adjust sliders and toggles as needed:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2240" height="888" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_651bb3f003815c00.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_743a501b5d6359ce.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_14eff608a826c599.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_14eff608a826c599.webp" alt="GitOps - ArgoCD Demo App UI - Load Generator" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this part, we:&lt;/p>
&lt;ul>
&lt;li>Deployed a demo application via Helm in ArgoCD&lt;/li>
&lt;li>Connected it to our PostgreSQL cluster&lt;/li>
&lt;li>Managed PostgreSQL users and access via GitHub and GitOps&lt;/li>
&lt;li>Imported a dataset and activated the traffic generator through the web UI&lt;/li>
&lt;/ul>
&lt;p>In &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-4-observability-and-monitoring-with-coroot-in-kubernetes/">Part 4&lt;/a>, we’ll deploy &lt;strong>Coroot&lt;/strong> for observability and profiling.&lt;br>
It’s an impressive tool for diagnosing behavior across services in the Kubernetes cluster.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>Opensource</category><category>GitOps</category><category>ArgoCD</category><media:thumbnail url="https://percona.community/blog/2025/07/gitops-part-3_hu_89d6e1d2addde317.jpg"/><media:content url="https://percona.community/blog/2025/07/gitops-part-3_hu_c3ba448f33f9a74f.jpg" medium="image"/></item><item><title>GitOps Journey: Part 2 – Deploying PostgreSQL with GitOps and ArgoCD</title><link>https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/</link><guid>https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/</guid><pubDate>Tue, 22 Jul 2025 00:00:30 UTC</pubDate><description>We’re now ready to deploy PostgreSQL 17 using GitOps — with ArgoCD, GitHub, and the Percona Operator for PostgreSQL.</description><content:encoded>&lt;p>We’re now ready to deploy &lt;strong>PostgreSQL 17&lt;/strong> using GitOps — with ArgoCD, GitHub, and the Percona Operator for PostgreSQL.&lt;/p>
&lt;p>If you’re a DBA, developer, DevOps engineer, or engineering manager, this part focuses on GitOps in action: deploying and managing a real database cluster using declarative infrastructure.&lt;/p>
&lt;p>In &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/">Part 1&lt;/a>, we set up the Kubernetes environment and installed ArgoCD.&lt;br>
Now it’s time to define and launch the PostgreSQL cluster — fully versioned and synced through Git.&lt;/p>
&lt;p>We’ll follow the official &lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/gke.html" target="_blank" rel="noopener noreferrer">Percona Operator documentation&lt;/a> and reference the &lt;a href="https://github.com/percona/percona-postgresql-operator" target="_blank" rel="noopener noreferrer">GitHub repository&lt;/a> to build out a production-grade setup.&lt;/p>
&lt;h2 id="preparing-the-environment">Preparing the Environment&lt;/h2>
&lt;p>There are multiple ways to install the Percona Operator and create a PostgreSQL cluster.&lt;br>
We’ll use the simplest and most GitOps-friendly approach:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Deploy the operator using &lt;code>deploy/bundle.yaml&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Deploy the cluster using &lt;code>deploy/cr.yaml&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Source files:&lt;/p>
&lt;ul>
&lt;li>&lt;code>https://github.com/percona/percona-postgresql-operator/blob/main/deploy/bundle.yaml&lt;/code>&lt;/li>
&lt;li>&lt;code>https://github.com/percona/percona-postgresql-operator/blob/main/deploy/cr.yaml&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="project-structure">Project Structure&lt;/h2>
&lt;p>Repository structure can vary depending on your services and infrastructure scale.&lt;br>
For this series, we’ll use:&lt;/p>
&lt;ul>
&lt;li>&lt;code>postgres/&lt;/code> → Contains all manifests related to PostgreSQL: the operator, clusters, backups&lt;/li>
&lt;li>&lt;code>apps/&lt;/code> → Contains ArgoCD application manifests that track changes in the repository&lt;/li>
&lt;/ul>
&lt;p>You’re free to choose a different structure. Just ensure all paths are correctly referenced in ArgoCD.&lt;/p>
&lt;h2 id="creating-the-postgres-directory-and-saving-manifests">Creating the Postgres Directory and Saving Manifests&lt;/h2>
&lt;p>You can manually download the files from GitHub or automate it via CLI:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mkdir postgres&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">curl -o postgres/bundle.yaml https://raw.githubusercontent.com/percona/percona-postgresql-operator/v2.7.0/deploy/bundle.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">curl -o postgres/cr.yaml https://raw.githubusercontent.com/percona/percona-postgresql-operator/v2.7.0/deploy/cr.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can also separately clone &lt;a href="https://github.com/percona/percona-postgresql-operator" target="_blank" rel="noopener noreferrer">the operator repository&lt;/a> and grab the necessary files from there.&lt;/p>
&lt;h2 id="creating-the-argocd-application-manifest">Creating the ArgoCD Application Manifest&lt;/h2>
&lt;p>This ArgoCD application will track the &lt;code>postgres/&lt;/code> directory and automatically sync changes from GitHub.&lt;/p>
&lt;p>Create the file: &lt;code>apps/argocd-postgres.yaml&lt;/code>&lt;/p>
&lt;p>Content:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: argoproj.io/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> project: default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> source:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repoURL: https://github.com/dbazhenov/percona-argocd-pg-coroot.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> targetRevision: main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> path: postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> destination:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: https://kubernetes.default.svc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> namespace: postgres-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncPolicy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> automated:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> prune: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> selfHeal: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> syncOptions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - CreateNamespace=true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ServerSideApply=true &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can also create this app manually via the ArgoCD UI or CLI, but using a manifest aligns better with GitOps principles.&lt;/p>
&lt;p>Double-check your:&lt;/p>
&lt;ul>
&lt;li>&lt;code>repoURL&lt;/code> → matches your GitHub repository&lt;/li>
&lt;li>&lt;code>path&lt;/code> → corresponds to your PostgreSQL manifest directory&lt;/li>
&lt;li>&lt;code>namespace&lt;/code> → targets the correct namespace for operator and cluster&lt;/li>
&lt;/ul>
&lt;h2 id="managing-argocd-sync-order-with-waves">Managing ArgoCD Sync Order with Waves&lt;/h2>
&lt;p>ArgoCD applies manifests based on &lt;code>sync-wave&lt;/code> annotations.&lt;/p>
&lt;ul>
&lt;li>The operator (&lt;code>bundle.yaml&lt;/code>) should be applied first&lt;/li>
&lt;li>The cluster (&lt;code>cr.yaml&lt;/code>) comes second&lt;/li>
&lt;/ul>
&lt;p>Add these annotations:&lt;/p>
&lt;p>&lt;strong>In &lt;code>bundle.yaml&lt;/code>:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> annotations:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> argocd.argoproj.io/sync-wave: "1"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>In &lt;code>cr.yaml&lt;/code>:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: cluster1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> annotations:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> argocd.argoproj.io/sync-wave: "5"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This ensures a stable deployment sequence.&lt;/p>
&lt;p>Later in the series (e.g. when installing Coroot), we’ll use a more advanced method: defining sync order via &lt;code>kustomization.yaml&lt;/code>.&lt;/p>
&lt;h2 id="reviewing-cluster-configuration">Reviewing Cluster Configuration&lt;/h2>
&lt;p>Before applying the manifests, review and adjust your cluster settings in &lt;code>cr.yaml&lt;/code>.&lt;/p>
&lt;p>Key defaults:&lt;/p>
&lt;ul>
&lt;li>&lt;code>name: cluster1&lt;/code> → Cluster name&lt;/li>
&lt;li>&lt;code>postgresVersion: 17&lt;/code> → PostgreSQL version&lt;/li>
&lt;/ul>
&lt;p>To keep the cluster lightweight and test horizontal scaling later, reduce replicas to 1:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">instances:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: instance1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">proxy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pgBouncer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can also configure resource limits, disk sizes, backups, and users in this file.&lt;/p>
&lt;h2 id="publishing-the-configuration-to-github">Publishing the Configuration to GitHub&lt;/h2>
&lt;p>Verify your repo status:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add files:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Review staged files:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected result:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">On branch main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your branch is up to date with 'origin/main'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Untracked files:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (use "git add &lt;file>..." to include in what will be committed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> apps/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> postgres/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nothing added to commit but untracked files present (use "git add" to track)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">On branch main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your branch is up to date with 'origin/main'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes to be committed:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (use "git restore --staged &lt;file>..." to unstage)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new file: apps/argocd-postgres.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new file: postgres/bundle.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new file: postgres/cr.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Commit:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git commit -m "Initial configuration of a Postgres cluster using Percona Operator for Postgres and ArgoCD"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Push to GitHub:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git push origin main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify files are published correctly in the repository.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2394" height="1012" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-pg-init_hu_9cb2d4078e93c26e.webp 768w, https://percona.community/blog/2025/07/gitops-github-pg-init_hu_12c1f95bc895d0be.webp 480w, https://percona.community/blog/2025/07/gitops-github-pg-init_hu_5363c716f9ce47cc.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-pg-init_hu_5363c716f9ce47cc.webp" alt="GitOps - Percona Operator for Postgres and PG Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="applying-the-argocd-app-manifest">Applying the ArgoCD App Manifest&lt;/h2>
&lt;p>To initiate the deployment, apply the previously created manifest:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl apply -f apps/argocd-postgres.yaml -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After a minute or two, the ArgoCD dashboard should display the synced PostgreSQL application and the deployed cluster.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1604" height="1110" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_88fc22241707d130.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_a46fff7b91758861.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_40185df5dd7f2c13.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_40185df5dd7f2c13.webp" alt="GitOps - ArgoCD app - Postgres" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3282" height="1844" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_ff8fd4ccc95f842c.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_9f764245487e8e8c.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_4761de8d2db1a5fc.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_4761de8d2db1a5fc.webp" alt="GitOps - ArgoCD app - Postgres - map" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="verifying-cluster-status">Verifying Cluster Status&lt;/h2>
&lt;p>Check the running pods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get pods -n postgres-operator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected results:&lt;/p>
&lt;ul>
&lt;li>One PostgreSQL instance pod&lt;/li>
&lt;li>One pgBouncer pod&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) kubectl get pods -n postgres-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-backup-5g98-5b29w 0/1 Completed 0 27m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-instance1-22vd-0 4/4 Running 0 28m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-pgbouncer-649b7cf845-fgs9l 2/2 Running 0 28m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-repo-host-0 2/2 Running 0 28m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-postgresql-operator-79f75d5f76-xjndr 1/1 Running 0 29m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="scaling-the-cluster-via-gitops">Scaling the Cluster via GitOps&lt;/h2>
&lt;p>Let’s test the GitOps model by updating the cluster configuration to increase replicas to 3.&lt;/p>
&lt;p>Edit &lt;code>postgres/cr.yaml&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> instances:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: instance1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> proxy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pgBouncer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Save the changes and push them:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add . &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git commit -m "Postgres cluster: Horizontal scaling from 1 replica to 3" &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git push origin main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>ArgoCD will automatically detect and apply this update.&lt;/p>
&lt;p>Expected results:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) git status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">On branch main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your branch is up to date with 'origin/main'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes not staged for commit:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (use "git add &lt;file>..." to update what will be committed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (use "git restore &lt;file>..." to discard changes in working directory)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> modified: postgres/cr.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">no changes added to commit (use "git add" and/or "git commit -a")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ✗ git commit -m "Postgres cluster: Horizontal scaling from 1 replica to 3"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[main 6b2dc98] Postgres cluster: Horizontal scaling from 1 replica to 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 file changed, 2 insertions(+), 2 deletions(-)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) git push origin main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enumerating objects: 7, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Counting objects: 100% (7/7), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Delta compression using up to 10 threads
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Compressing objects: 100% (4/4), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing objects: 100% (4/4), 435 bytes | 435.00 KiB/s, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To github.com:dbazhenov/percona-argocd-pg-coroot.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 81ae9e8..6b2dc98 main -> main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected results on GitHub Repo:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1918" height="814" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-scale_hu_75d7ceb0d410ba73.webp 768w, https://percona.community/blog/2025/07/gitops-github-scale_hu_c61828640de654ab.webp 480w, https://percona.community/blog/2025/07/gitops-github-scale_hu_79e73072768a3b3f.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-scale_hu_79e73072768a3b3f.webp" alt="GitOps - ArgoCD app - Postgres scale - GitHub" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="confirming-the-update-in-argocd">Confirming the Update in ArgoCD&lt;/h2>
&lt;p>In the ArgoCD UI, you should now see the application synced to the latest commit with the updated replica count.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2346" height="1438" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_ff0d90769c5697b4.webp 768w, https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_7f6c555e94fc5fd2.webp 480w, https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_98b7696225caf1fa.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_98b7696225caf1fa.webp" alt="GitOps - ArgoCD apps - Postgres scale - Argo" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Verify pods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get pods -n postgres-operator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected result — 3 PostgreSQL pods and 3 pgBouncer pods.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) kubectl get pods -n postgres-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-backup-5g98-5b29w 0/1 Completed 0 38m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-instance1-22vd-0 4/4 Running 0 39m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-instance1-q2r4-0 4/4 Running 0 3m38s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-instance1-r4s2-0 4/4 Running 0 3m39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-pgbouncer-649b7cf845-9cppx 2/2 Running 0 3m37s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-pgbouncer-649b7cf845-fgs9l 2/2 Running 0 39m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-pgbouncer-649b7cf845-tkf9z 2/2 Running 0 3m36s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster1-repo-host-0 2/2 Running 0 39m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-postgresql-operator-79f75d5f76-xjndr 1/1 Running 0 40m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="whats-next">What’s Next&lt;/h2>
&lt;p>We’ve successfully installed the Percona Operator for PostgreSQL and deployed a cluster using GitHub and ArgoCD.&lt;/p>
&lt;p>We also verified GitOps functionality by scaling the cluster through Git-controlled configuration.&lt;br>
All changes are tracked, versioned, and declarative — a solid foundation for modern infrastructure management.&lt;/p>
&lt;p>To continue experimenting:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/connect.html" target="_blank" rel="noopener noreferrer">Connect to the Cluster&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/users.html" target="_blank" rel="noopener noreferrer">Manage Users&lt;/a>. Note: the default user does not have SUPERUSER privileges. If your app requires creating databases, you’ll need to configure appropriate roles.&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/expose.html" target="_blank" rel="noopener noreferrer">Expose the Cluster&lt;/a>. So you can connect from external clients or apps.&lt;/li>
&lt;/ol>
&lt;p>We’ll do exactly that in &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/">the next part&lt;/a> — by deploying a demo application and connecting it to the database using GitOps.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>Opensource</category><category>GitOps</category><category>ArgoCD</category><media:thumbnail url="https://percona.community/blog/2025/07/gitops-part-2_hu_2d6aac6b62668af3.jpg"/><media:content url="https://percona.community/blog/2025/07/gitops-part-2_hu_3560831b9d7392f7.jpg" medium="image"/></item><item><title>GitOps Journey: Part 1 – Getting Started with ArgoCD and GitHub</title><link>https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/</link><guid>https://percona.community/blog/2025/07/22/gitops-journey-part-1-getting-started-with-argocd-and-github/</guid><pubDate>Tue, 22 Jul 2025 00:00:10 UTC</pubDate><description>Welcome to GitOps Journey — a hands-on guide to setting up infrastructure in Kubernetes using Git and automation.</description><content:encoded>&lt;p>Welcome to &lt;strong>GitOps Journey&lt;/strong> — a hands-on guide to setting up infrastructure in Kubernetes using Git and automation.&lt;/p>
&lt;p>GitOps has gained traction alongside Kubernetes, CI/CD, and declarative provisioning.&lt;br>
You’ve probably seen it mentioned in blog posts, tech talks, or conference slides — but what does it actually look like in practice?&lt;/p>
&lt;p>We’ll start from scratch: prepare a cluster, deploy a PostgreSQL database, run a demo app, and set up observability — all managed via Git and GitHub using ArgoCD.&lt;/p>
&lt;h2 id="what-well-build">What We’ll Build&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>ArgoCD&lt;/strong> — syncs manifests from a GitHub repository to your cluster&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>PostgreSQL&lt;/strong> — a production-ready database using Percona Operator&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Demo App&lt;/strong> — a real Go-based web app connected to the database&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Coroot&lt;/strong> — an open-source tool for monitoring performance, logs, and service behavior&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>This series is for anyone new to GitOps or Kubernetes.&lt;br>
Each part includes clear steps, real-world YAML, and examples you can run yourself.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>This is Part 1 of the GitOps Journey.&lt;/strong>&lt;br>
If you already have ArgoCD and a working Kubernetes cluster, you can skip ahead:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/">Part 2 – Deploying PostgreSQL with Percona Operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/">Part 3 – Connecting a Real App to the Cluster&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-4-observability-and-monitoring-with-coroot-in-kubernetes/">Part 4 – Observability with Coroot&lt;/a>&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;blockquote>
&lt;p>Copilot assisted with formatting, Markdown structure, and translation.&lt;br>
All ideas, architecture decisions, and hands-on implementation were created by Daniil Bazhenov.&lt;/p>&lt;/blockquote>
&lt;p>Otherwise, let’s start by preparing the cluster and setting up ArgoCD.&lt;/p>
&lt;h2 id="creating-a-kubernetes-cluster">Creating a Kubernetes Cluster&lt;/h2>
&lt;p>I’ll be using Google Kubernetes Engine (GKE), but you can use AWS, DigitalOcean, or even run Minikube locally.&lt;/p>
&lt;p>You’ll also need these CLI tools installed on your machine:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://kubernetes.io/docs/tasks/tools/#kubectl" target="_blank" rel="noopener noreferrer">kubectl&lt;/a> - The official CLI tool for Kubernetes — used to manage clusters, view resources, apply manifests, and more.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://helm.sh/docs/intro/install/" target="_blank" rel="noopener noreferrer">helm&lt;/a> - A package manager for Kubernetes — lets you install complex apps using reusable charts (like PostgreSQL, monitoring tools, etc.)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>I use the following command to create a cluster in GKE&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">gcloud container clusters create dbazhenov-demo \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --project percona-product \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --zone us-central1-a \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --cluster-version 1.30 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --machine-type n1-standard-8 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --num-nodes=3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To delete the cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">gcloud container clusters delete dbazhenov-demo --zone us-central1-a&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Note: This command doesn’t remove your LoadBalancers, so I prefer deleting them manually in Google Cloud’s web console to ensure no resources are left running post-experiment.&lt;/p>&lt;/blockquote>
&lt;p>Here’s the resulting setup:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-dbazhenov-demo-default-pool-b1b48316-8nrj Ready &lt;none> 6m7s v1.30.12-gke.1279000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-dbazhenov-demo-default-pool-b1b48316-8v14 Ready &lt;none> 6m6s v1.30.12-gke.1279000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-dbazhenov-demo-default-pool-b1b48316-zg6z Ready &lt;none> 6m7s v1.30.12-gke.1279000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="installing-argocd">Installing ArgoCD&lt;/h2>
&lt;p>We’ll begin with ArgoCD, the GitOps engine that will deploy:&lt;/p>
&lt;p>PostgreSQL database cluster&lt;/p>
&lt;p>A demo app to simulate real usage&lt;/p>
&lt;p>Coroot for monitoring and profiling workloads&lt;/p>
&lt;p>ArgoCD supports multiple deployment methods — we’ll experiment with different ones during this series.&lt;/p>
&lt;p>Install ArgoCD (&lt;a href="https://argo-cd.readthedocs.io/en/stable/getting_started/" target="_blank" rel="noopener noreferrer">based on official docs&lt;/a>):&lt;/p>
&lt;ol>
&lt;li>Create namespace:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl create namespace argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Deploy ArgoCD:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check the pods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get pods -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output: ArgoCD components running (server, repo, redis, controllers, etc.)&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ kubectl get pods -n argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-application-controller-0 1/1 Running 0 57s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-applicationset-controller-6d569f7895-89kgk 1/1 Running 0 64s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-dex-server-5b44d67df9-p42z5 1/1 Running 0 62s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-notifications-controller-5865dfbc8-gqzwt 1/1 Running 0 61s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-redis-6bb7987874-99j59 1/1 Running 0 61s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-repo-server-df8b9fd78-64czj 1/1 Running 0 60s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-server-6d896f6785-82tf2 1/1 Running 0 59s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Access ArgoCD UI&lt;/li>
&lt;/ol>
&lt;p>You have two options (or more):&lt;/p>
&lt;ul>
&lt;li>Port forwarding (local only)&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl port-forward svc/argocd-server -n argocd 8080:443&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>Internet-accessible LoadBalancer&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I will use Load Balancer by executing the command above, you need to wait a few minutes to get the IP address.&lt;/p>
&lt;p>Let’s get the IP address of the ArgoCD service in the EXTERNAL-IP field.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get svc argocd-server -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ kubectl get svc argocd-server -n argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">argocd-server LoadBalancer 34.118.234.162 34.132.39.194 80:30549/TCP,443:32146/TCP 9m51s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Access the UI in your browser using the IP.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2032" height="1444" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-login_hu_ae262538d39cc378.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-login_hu_6a0c1cec6d5835a5.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-login_hu_e4619940afda3630.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-login_hu_e4619940afda3630.webp" alt="GitOps - ArgoCD UI" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>Getting Started with ArgoCD Login&lt;/li>
&lt;/ol>
&lt;p>Download Argo CD CLI&lt;/p>
&lt;p>Install ArgoCD CLI (&lt;a href="https://argo-cd.readthedocs.io/en/stable/getting_started/#2-download-argo-cd-cli" target="_blank" rel="noopener noreferrer">see instructions&lt;/a>).&lt;/p>
&lt;p>Get the initial password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">argocd admin initial-password -n argocd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>ArgoCD recommends changing it to a new secure password, which we will do.&lt;/p>
&lt;p>Log in via CLI:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">argocd login 34.132.39.194 --insecure&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Authorize using initial-password and user admin and execute the password update command&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">argocd account update-password&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>All the steps to get and update your password are below.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ argocd admin initial-password -n argocd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">0mxV6IVcF3qZDR-O
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This password must be only used for first time login. We strongly recommend you update the password using `argocd account update-password`.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ argocd login 34.132.39.194 --insecure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Username: admin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Password:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'admin:login' logged in successfully
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Context '34.132.39.194' updated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗ argocd account update-password
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*** Enter password of currently logged in user (admin):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*** Enter new password for user admin:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*** Confirm new password for user admin:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Password updated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Context '34.132.39.194' updated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ community git:(blog_argocd_pg) ✗&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="5">
&lt;li>Now log into the ArgoCD web UI using admin and your new password.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2254" height="1230" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_26a390634918988b.webp 768w, https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_c82f359b201a558.webp 480w, https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_8d203acf1d8728c0.webp 1400w" src="https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_8d203acf1d8728c0.webp" alt="GitOps: ArgoCD web UI" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Welcome to the ArgoCD interface, we don’t have any applications right now, we will install them later.&lt;/p>
&lt;h2 id="setting-up-github-repo">Setting Up GitHub Repo&lt;/h2>
&lt;p>We’ll need a GitHub repo to store infrastructure manifests. ArgoCD will sync from this repo and apply changes.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Install git and create a GitHub account&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add your SSH key to your GitHub profile. &lt;a href="https://github.com/settings/keys" target="_blank" rel="noopener noreferrer">GitHub SSH settings&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a new GitHub repository&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>I recommend a public repo for this educational project — no secrets will be committed, and it simplifies ArgoCD setup. Plus, it earns you some green squares on GitHub. If you go with a private repo, make sure it’s properly linked in ArgoCD.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1744" height="1376" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-new-repo_hu_9594b924f317c30d.webp 768w, https://percona.community/blog/2025/07/gitops-github-new-repo_hu_deca0d60bb96f644.webp 480w, https://percona.community/blog/2025/07/gitops-github-new-repo_hu_88e363c983bbc429.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-new-repo_hu_88e363c983bbc429.webp" alt="GitOps: GitHub Repo Creation" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>Clone the repo:&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1858" height="868" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/07/gitops-github-clone_hu_df0b459af518d0df.webp 768w, https://percona.community/blog/2025/07/gitops-github-clone_hu_607b64ccd8de443d.webp 480w, https://percona.community/blog/2025/07/gitops-github-clone_hu_f0a07d19bd42378a.webp 1400w" src="https://percona.community/blog/2025/07/gitops-github-clone_hu_f0a07d19bd42378a.webp" alt="GitOps: GitHub Clone" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Clone the repository using an SSH address.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git clone git@github.com:dbazhenov/percona-argocd-pg-coroot.git&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Navigate to the project directory&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cd percona-argocd-pg-coroot&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ gitops git clone git@github.com:dbazhenov/percona-argocd-pg-coroot.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Cloning into 'percona-argocd-pg-coroot'...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Enumerating objects: 3, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Counting objects: 100% (3/3), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Compressing objects: 100% (2/2), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Receiving objects: 100% (3/3), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ gitops cd percona-argocd-pg-coroot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main) ls
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ percona-argocd-pg-coroot git:(main)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We’ve prepared everything to launch our GitOps-powered infrastructure:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Kubernetes cluster&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ArgoCD deployed&lt;/p>
&lt;/li>
&lt;li>
&lt;p>GitHub repo ready&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>In the next posts, we’ll deploy &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-2-deploying-postgresql-with-gitops-and-argocd/">the PostgreSQL cluster&lt;/a>, &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-3-deploying-a-load-generator-and-connecting-to-postgresql/">the demo app&lt;/a>, and add &lt;a href="https://percona.community/blog/2025/07/22/gitops-journey-part-4-observability-and-monitoring-with-coroot-in-kubernetes/">Coroot monitoring&lt;/a>.&lt;/p>
&lt;p>Stay tuned!&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PostgreSQL</category><category>Opensource</category><category>GitOps</category><category>ArgoCD</category><media:thumbnail url="https://percona.community/blog/2025/07/gitops-part-1_hu_1404141b76c22066.jpg"/><media:content url="https://percona.community/blog/2025/07/gitops-part-1_hu_4e4052d3f7870720.jpg" medium="image"/></item><item><title>Using replicaSetHorizons in MongoDB</title><link>https://percona.community/blog/2025/07/22/using-replicasethorizons-in-mongodb/</link><guid>https://percona.community/blog/2025/07/22/using-replicasethorizons-in-mongodb/</guid><pubDate>Tue, 22 Jul 2025 00:00:00 UTC</pubDate><description>When running MongoDB replica sets in containerized environments like Docker or Kubernetes, making nodes reachable from inside the cluster as well as from external clients can be a challenge. To solve this problem, this post is going to explain the horizons feature of Percona Server for MongoDB.</description><content:encoded>&lt;p>When running MongoDB replica sets in containerized environments like Docker or Kubernetes, making nodes reachable from inside the cluster as well as from external clients can be a challenge. To solve this problem, this post is going to explain the horizons feature of &lt;a href="https://docs.percona.com/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="560" height="385" sizes="(max-width: 560px) 100vw, 560px" srcset="https://percona.community/blog/2025/07/ivan_cover_hu_afa8afa1b30697b1.webp 480w, https://percona.community/blog/2025/07/ivan_cover_hu_b7afd7179eba498e.webp 560w" src="https://percona.community/blog/2025/07/ivan_cover_hu_b7afd7179eba498e.webp" alt="Using_replicaSetHorizons_in_MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Let’s start by looking at what happens behind the scenes when you connect to a replicaset URI.&lt;/p>
&lt;h2 id="node-auto-discovery">Node auto-discovery&lt;/h2>
&lt;p>After connecting with a replset URI, the driver discovers the list of actual members by running the db.hello() command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mongosh "mongodb://mongo1-internal:27017/?replicaSet=rs0"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rs0 [direct: primary] test> db.hello()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> topologyVersion: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> processId: ObjectId('6877b5e18a13d54b752ff25c'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> counter: Long('6')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: [ 'mongo1-internal:27017', 'mongo2-internal:27017', 'mongo3-internal:27017' ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The list of hosts returned contains the name of each member as you provided it to the rs.initialize() command.&lt;/p>
&lt;h2 id="the-node-identity-crisis">The node identity crisis&lt;/h2>
&lt;p>The names are resolvable inside the same network, so all is well in this case. But what happens when connecting from outside?&lt;/p>
&lt;p>Typically you would be using names like mongo1-external.mydomain.com that correctly point to the external IP addresses of the members. The problem is that after the initial connection is made, the driver will perform auto-discovery and try to connect to the names as reported by db.hello(). These are not resolvable from outside.&lt;/p>
&lt;p>What if we connect by IP address directly? again, the driver will get the names from the list above, try to reach those and fail after the initial connection is made:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mongosh mongodb://user:pass@10.30.50.155:32768/?replicaSet=rs0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Current Mongosh Log ID: 6849eb15ba228be45a69e327
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connecting to: mongodb://&lt;credentials>@10.30.50.155:32768/?replicaSet=rs0&amp;appName=mongosh+2.5.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MongoNetworkError: getaddrinfo ENOTFOUND mongo1-internal&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Even though mongo1-internal is not part of the connection string, the driver tries to reach it. So if the replica set members advertise their internal IPs or DNS names, clients outside can’t connect unless they can resolve that same name. We could work around that, but there’s another issue: the ports.&lt;/p>
&lt;h2 id="the-port-issue">The port issue&lt;/h2>
&lt;p>In the containerized world, it is likely that you set up your containers to use default port 27017. However they might be mapped to a different external port, since you have to avoid port collisions (think about the case where containers are co-located in the same host).&lt;/p>
&lt;p>We need a way for replica set members to identify themselves with different names and ports, depending on whether the client is in the same network or outside. A concept similar to split-brain DNS.&lt;/p>
&lt;h2 id="what-is-horizons">What is Horizons?&lt;/h2>
&lt;p>Horizons is a MongoDB feature that allows replica set members to advertise different identities depending on the client’s access context, such as internal versus external networks.&lt;/p>
&lt;p>With this, you can make the same MongoDB replica set usable from:&lt;/p>
&lt;ul>
&lt;li>Internal container network (using internal hostnames/IPs)&lt;/li>
&lt;li>External applications (using public IPs or DNS names)&lt;/li>
&lt;/ul>
&lt;p>MongoDB’s horizons rely on Server Name Indication (SNI) during the TLS handshake to determine which hostname and port to advertise. At connection time, clients present the hostname they used, and MongoDB uses that to return the proper set of endpoints. For that reason TLS is required in order for horizons to work.&lt;/p>
&lt;p>Let’s walk through an example.&lt;/p>
&lt;h2 id="example-scenario-mongodb-replica-set-in-docker">Example Scenario: MongoDB Replica Set in Docker&lt;/h2>
&lt;p>You can run the following steps on your local machine to test the feature.&lt;/p>
&lt;h3 id="get-your-certificates-ready">Get your certificates ready&lt;/h3>
&lt;p>Let’s start by creating the required CA and certificates using &lt;a href="https://github.com/cloudflare/cfssl" target="_blank" rel="noopener noreferrer">Cloudflare’s PKI and TLS toolkit&lt;/a>.&lt;/p>
&lt;h4 id="step-1-create-ca-csrjson">Step 1: Create ca-csr.json&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mkdir certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cd certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tee ca-csr.json &lt;&lt;EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "CN": "MyTestCA",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "key": { "algo": "rsa", "size": 2048 },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "names": [{ "C": "US", "ST": "CA", "L": "SF", "O": "Acme", "OU": "MongoDB CA" }]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EOF&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Generate the CA:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cfssl gencert -initca ca-csr.json | cfssljson -bare ca&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This creates:&lt;/p>
&lt;ul>
&lt;li>ca.pem — CA certificate&lt;/li>
&lt;li>ca-key.pem — CA private key&lt;/li>
&lt;/ul>
&lt;h4 id="step-2-create-server-csrjson-for-each-server-specifying-both-internal-and-external-names-in-the-hosts-section-so-that-our-certificate-is-valid-for-everything">Step 2: Create server-csr.json for each server, specifying both internal and external names in the “hosts” section so that our certificate is valid for everything.&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">for i in 1 2 3; do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name="mongo$i"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tee "${name}-csr.json" &lt;&lt;EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "CN": "${name}",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "hosts": ["${name}", "${name}.internal", "localhost", "127.0.0.1"],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "key": { "algo": "rsa", "size": 2048 },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "names": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { "O": "MongoDB", "OU": "Database", "L": "Internal", "ST": "DC", "C": "US" }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">done&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="step-3-generate-certificates-using-cfssl">Step 3: Generate certificates using CFSSL&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">for i in 1 2 3; do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name="mongo$i"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cfssl gencert \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -ca=ca.pem -ca-key=ca-key.pem \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -config=&lt;(cat &lt;&lt;'JSON'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "signing": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "default": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "expiry": "8760h",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "usages": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "signing",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "key encipherment",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "server auth",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "client auth"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">JSON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">) "${name}-csr.json" | cfssljson -bare "${name}"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat "${name}.pem" "${name}-key.pem" > "${name}-combined.pem"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">done
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cd ..&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Resulting files:&lt;/p>
&lt;ul>
&lt;li>mongo{1,2,3}.pem — cert for server&lt;/li>
&lt;li>mongo{1,2,3}-key.pem, key for server&lt;/li>
&lt;li>mongo{1,2,3}-combined.pem, both in a single file as expected by mongo&lt;/li>
&lt;/ul>
&lt;h3 id="docker-compose-setup">Docker Compose Setup&lt;/h3>
&lt;p>Create a file with docker compose configuration:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tee test-horizons.yml &lt;&lt;EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">name: horizons
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongo1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> container_name: mongo1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: percona/percona-server-mongodb:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./certs:/certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - "27017:27017"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> command: >
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongod --replSet rs0 --bind_ip_all
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsMode requireTLS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCertificateKeyFile /certs/mongo1-combined.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCAFile /certs/ca.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongo2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> container_name: mongo2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: percona/percona-server-mongodb:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./certs:/certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - "27018:27017"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> command: >
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongod --replSet rs0 --bind_ip_all
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsMode requireTLS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCertificateKeyFile /certs/mongo2-combined.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCAFile /certs/ca.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongo3:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> container_name: mongo3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: percona/percona-server-mongodb:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./certs:/certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - "27019:27017"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> command: >
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongod --replSet rs0 --bind_ip_all
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsMode requireTLS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCertificateKeyFile /certs/mongo3-combined.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tlsCAFile /certs/ca.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">networks:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> default:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> driver: bridge
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EOF&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here we are mapping our containers to ports 27017, 27018 and 27109 externally.&lt;/p>
&lt;p>Now, start the services:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker-compose -f test-horizons.yml up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="initiate-the-replica-set-with-horizons">Initiate the Replica Set with Horizons&lt;/h3>
&lt;p>Now let’s initiate the replica set with different host names and ports for external access.&lt;/p>
&lt;p>Launch a shell into one of the containers:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker exec -it mongo1 /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Authenticate and initialize the replica set with this config:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mongosh --tls --tlsCertificateKeyFile /certs/mongo1-combined.pem --tlsAllowInvalidCertificates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rs.initiate({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: "rs0",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> members: [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host: "mongo1:27017",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> horizons: { external: "localhost:27017" }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host: "mongo2:27017",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> horizons: { external: "localhost:27018" }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host: "mongo3:27017",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> horizons: { external: "localhost:27019" }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: The “horizon” field here maps the external context to a different address than the internal one. Since we are going to test connecting from the local machine directly to the containers, set the horizons to localhost and the mapped ports.&lt;/p>
&lt;h3 id="connect-from-inside-docker">Connect from Inside Docker&lt;/h3>
&lt;p>Spin up a new containerized client, or use one of the existing MongoDB containers:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker exec -it mongo1 mongosh --host rs0/mongo1:27017,mongo2:27017,mongo3:27017 --tls --tlsCertificateKeyFile /certs/mongo1-combined.pem --tlsCAFile /certs/ca.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Current Mongosh Log ID: 6877deab6568339f46dfd9c4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connecting to: mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0&amp;tls=true&amp;tlsCertificateKeyFile=%2Fcerts%2Fmongo1-combined.pem&amp;tlsCAFile=%2Fcerts%2Fca.pem&amp;appName=mongosh+2.5.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using MongoDB: 8.0.8-3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using Mongosh: 2.5.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rs0 [primary] test>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It connects using internal Docker hostnames.&lt;/p>
&lt;h3 id="connect-from-outside-docker">Connect from Outside Docker&lt;/h3>
&lt;p>From your local machine:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mongosh "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0" --tls --tlsCertificateKeyFile /certs/mongo1-combined.pem --tlsCAFile /certs/ca.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Current Mongosh Log ID: 6877defabc3f9a2d054a1296
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connecting to: mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0&amp;serverSelectionTimeoutMS=2000&amp;tls=true&amp;tlsCertificateKeyFile=certs%2Fmongo1-combined.pem&amp;tlsCAFile=certs%2Fca.pem&amp;appName=mongosh+2.3.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using MongoDB: 8.0.8-3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using Mongosh: 2.3.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rs0 [primary] test>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="check-the-identities-returned">Check the identities returned&lt;/h3>
&lt;p>As we have seen, MongoDB will resolve the external horizon names and connect successfully in both cases. You can verify the advertised hostnames and ports for the external connection:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">rs0 [primary] test> db.hello()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> topologyVersion: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> processId: ObjectId('6877de4c632adf89fb590f38'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> counter: Long('6')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: [ 'localhost:27017', 'localhost:27018', 'localhost:27019' ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> setName: 'rs0',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> setVersion: 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> isWritablePrimary: true,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secondary: false,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primary: 'localhost:27017',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> me: 'localhost:27017',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Versus the internal case:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">rs0 [primary] test> db.hello()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> topologyVersion: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> processId: ObjectId('6877de4c632adf89fb590f38'),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> counter: Long('6')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: [ 'mongo1:27017', 'mongo2:27017', 'mongo3:27017' ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> setName: 'rs0',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> setVersion: 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> isWritablePrimary: true,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secondary: false,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primary: 'mongo1:27017',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> me: 'mongo1:27017',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The horizons feature in MongoDB is a powerful tool to bridge the gap between internal and external connectivity, especially in containerized or multi-network deployments.&lt;/p>
&lt;p>Horizon also has following limitations:&lt;/p>
&lt;ul>
&lt;li>Using horizons is only possible with TLS connections&lt;/li>
&lt;li>Duplicating domain names in horizons is not allowed by MongoDB&lt;/li>
&lt;li>Using IP addresses in horizons definitions is not allowed by MongoDB&lt;/li>
&lt;li>Horizons should be set for all members of a replica set, or not set at all&lt;/li>
&lt;/ul>
&lt;p>This feature is not listed in the official MongoDB documentation for some reason, however it is available in both Percona Server for MongoDB and MongoDB Community Edition. Also, Kubernetes users rejoice! &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/expose.html?h=split#exposing-replica-set-with-split-horizon-dns" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB supports horizons&lt;/a> since version 1.16.&lt;/p></content:encoded><author>Ivan Groenewold</author><category>MongoDB</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/07/ivan_cover_hu_252d99e16859146e.jpg"/><media:content url="https://percona.community/blog/2025/07/ivan_cover_hu_51c601d52e2aec6.jpg" medium="image"/></item><item><title>Active-active replication - the hidden costs and complexities</title><link>https://percona.community/blog/2025/07/10/active-active-replication-the-hidden-costs-and-complexities/</link><guid>https://percona.community/blog/2025/07/10/active-active-replication-the-hidden-costs-and-complexities/</guid><pubDate>Thu, 10 Jul 2025 00:00:00 UTC</pubDate><description>In Part 1 of this series, we discussed what active-active databases are and identified some “good” reasons for considering them, primarily centered around extreme high availability and critical write availability during regional outages. Now, let’s turn our attention to the less compelling justifications and the substantial challenges that come with implementing such a setup.</description><content:encoded>&lt;p>In &lt;a href="https://percona.community/blog/2025/06/18/postgresql-active-active-replication-do-you-really-need-it/">Part 1&lt;/a> of this series, we discussed what active-active databases are and identified some “good” reasons for considering them, primarily centered around extreme high availability and critical write availability during regional outages. Now, let’s turn our attention to the less compelling justifications and the substantial challenges that come with implementing such a setup.&lt;/p>
&lt;h2 id="what-are-bad-reasons">What are “bad” reasons?&lt;/h2>
&lt;h3 id="1-scaling-write-throughput">1. Scaling write throughput&lt;/h3>
&lt;p>Trying to scale your write capacity by deploying active-active across regions may sound like a clean horizontal solution, but it is rarely that simple. Write coordination, conflict resolution, and replication overhead introduce latency that defeats the purpose. If you are thinking about low latency writes between regions like Australia and the US, keep in mind that you will still be paying the round-trip cost, typically 150-200ms+, to maintain consistency. Physics don’t do any favors. Even if you have multiple primaries, unless you accept weaker consistency or potential conflicts, your writes will not scale linearly. In many real-world cases, throughput actually suffers compared to a well tuned primary replica setup. If your real goal is better throughput, you are usually better served by:&lt;/p>
&lt;ul>
&lt;li>Regional read replicas&lt;/li>
&lt;li>Sharding&lt;/li>
&lt;li>Task queuing and eventual delegation&lt;/li>
&lt;/ul>
&lt;h3 id="2-performance">2. Performance&lt;/h3>
&lt;p>Performance is often used as a vague justification. But active-active is not a panaceum for general slowness. If the issue is database bottlenecks, reasons may be as basic as not enough work spent on data structuring, indexing, query tuning or scaling reads before jumping into multi primary deployments. Way too often we find that performance problems come not from scale limits, but from poor design and neglected maintenance. We are in 2025 and lessons like &lt;a href="https://www.depesz.com/2007/07/05/how-to-insert-data-to-database-as-fast-as-possible" target="_blank" rel="noopener noreferrer">this classic one on fast inserts&lt;/a> are still painfully relevant. If what you are really facing is application level latency, that is a different challenge. And even then, active-active with strong guarantees often adds more latency, not less.&lt;/p>
&lt;h3 id="3-load-balancing">3. Load balancing&lt;/h3>
&lt;p>You don’t need multi-primary to load balance. If your goal is spreading traffic more evenly, and reads are your bottleneck, there are simpler and much safer ways to get there. That’s what read replicas are for. If what you are looking for is distributing writes, please re-read point 1: synchronizing state across regions adds cost, complexity, and consistency challenges. Multi-master does not eliminate contention, it just moves it. In these situations you will have conflicts, some due to lag, some for other reasons. Resolution logic becomes critical. Conflicts need to be logged and their resolution auditable.&lt;/p>
&lt;h3 id="4-because-other-systems-did-it">4. Because other systems did it&lt;/h3>
&lt;p>The fact that multi master was possible in other systems is not, on its own, a good reason to adopt it. If you have worked with Oracle RAC or GoldenGate, you likely remember the long nights of debugging conflicts, performance issues, the layers of failover logic, the dedicated teams just to keep things running smoothly. These systems made big promises, but keeping them stable in production was rarely simple or cheap. Copying those patterns means copying those problems. And trying to reproduce them with PostgreSQL, or some other modern databases, often misses the point. This is our chance to rethink how we build, not repeat the same mistakes. If you are going to pay the cost of active active, make sure you are doing it for the right reasons, not just because it looks like something you are used to.&lt;/p>
&lt;h2 id="the-cost-the-painful-side-of-active-active">The cost, the painful side of active-active&lt;/h2>
&lt;p>Let’s be very clear: active-active is not the shortcut it often appears to be. It’s not simply “HA but better.” It represents a fundamental system design change with significant, ongoing costs across engineering, operations, and product management. While it might be the only viable option in highly specific situations, by betting on extreme reliability, you are signing up for a hell of a lot of complexity.&lt;/p>
&lt;p>Much of what makes active-active the &lt;em>wrong&lt;/em> choice in typical scenarios comes down to these costs. So expect some repetition, that’s by design, because the price &lt;em>is&lt;/em> the problem.&lt;/p>
&lt;h3 id="1-conflict-resolution-is-a-problem-of-its-own">1. Conflict resolution is a problem of it’s own&lt;/h3>
&lt;p>Every multi-primary system has to answer one question: what happens when two nodes write to the same row at the same time?
PostgreSQL doesn’t have conflict resolution built-in. You can bolt it on with tools like pgactive or pglogical2, but none of them are perfect, far from it.&lt;/p>
&lt;p>The most common approaches applied:&lt;/p>
&lt;ul>
&lt;li>Last write wins (based on timestamps).&lt;/li>
&lt;li>Source priority (some nodes win conflicts).&lt;/li>
&lt;li>Application-defined merge logic.&lt;/li>
&lt;/ul>
&lt;p>They all come with trade-offs. None of them are free.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1024" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2025/07/PG_conflict_hu_627dbfbea0aa4515.webp 768w, https://percona.community/blog/2025/07/PG_conflict_hu_c6026d670300b570.webp 480w, https://percona.community/blog/2025/07/PG_conflict_hu_5e917bc079b11708.webp 1024w" src="https://percona.community/blog/2025/07/PG_conflict_hu_5e917bc079b11708.webp" alt="PG Conflict" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Consider how other systems handle this is very often not conflict handling but conflict avoidance:&lt;/p>
&lt;ul>
&lt;li>CockroachDB avoids exposing raw conflict handling to users by using strict serializability and sacrificing performance&lt;/li>
&lt;li>Oracle RAC handles it via centralized locking over a shared-disk architecture (and even that causes headaches)&lt;/li>
&lt;li>CouchDB, which embraces multi-master natively, makes conflict resolution a first-class application responsibility and warns users upfront&lt;/li>
&lt;/ul>
&lt;p>Unless your app is very carefully designed to dodge conflicts, say, by writing to disjoint subsets of data, you’re going to run into this. Probably when it’s already too late and your data’s out of sync.&lt;/p>
&lt;h3 id="2-infrastructure--networking-costs">2. Infrastructure &amp; networking costs&lt;/h3>
&lt;p>Running active-active across regions increases your infra footprint and cost. Specifically:&lt;/p>
&lt;ul>
&lt;li>More powerful machines or more of them – each node handles writes and replication&lt;/li>
&lt;li>Higher network bandwidth usage – especially for busy systems or cross-region replication&lt;/li>
&lt;li>Premium network setups – often needed for low-latency consistency&lt;/li>
&lt;li>Increased complexity in monitoring – replication lag, node health, consistency checks&lt;/li>
&lt;/ul>
&lt;p>These costs aren’t just financial, they bleed into reliability and operational overhead too.&lt;/p>
&lt;h3 id="3-operational-and-management-overhead">3. Operational and management overhead&lt;/h3>
&lt;p>Beyond setup, day-2 operations are where the real pain begins:&lt;/p>
&lt;ul>
&lt;li>Deployments become dangerous – schema or app changes can break replication or trigger conflicts&lt;/li>
&lt;li>Debugging is harder – tracing transactions across writeable nodes adds layers to incident response&lt;/li>
&lt;li>Staffing costs rise – managing active-active needs deeper expertise in distributed systems&lt;/li>
&lt;li>Auditing grows in importance – proving data consistency across nodes becomes a continuous task&lt;/li>
&lt;/ul>
&lt;p>Let’s be honest, what was hard before is now harder, and even the routine becomes risky.&lt;/p>
&lt;h2 id="dont-kill-the-messenger">Don’t kill the messenger&lt;/h2>
&lt;p>I love food. Cooking, eating, talking about it. Anyone I’ve worked with can vouch that I’m a foodie at heart. I like spicy food, really spicy. In my kitchen, you’ll find everything from Sichuan pepper to naga jolokia “ghost pepper”, from kala namak to asafoetida. These aren’t ingredients you throw into every dish. The right spice, at the wrong time, ruins everything. The same spice, used precisely, can turn something ordinary into magic.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1024" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2025/07/PG_chili_hu_a0c1dbf50b8b288e.webp 768w, https://percona.community/blog/2025/07/PG_chili_hu_1c6f99f6f46212b1.webp 480w, https://percona.community/blog/2025/07/PG_chili_hu_585f41acab6f005f.webp 1024w" src="https://percona.community/blog/2025/07/PG_chili_hu_585f41acab6f005f.webp" alt="PG Chili" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>What’s exactly how active-active works. It’s not bad by nature, it’s just very specific. You need to understand the dish, the eater, and the context. If you add ghost pepper to scrambled eggs because someone said it’s “cool,” you’re very likely going to regret it. Same goes for multi-primary setups.&lt;/p>
&lt;p>Know what you’re in for. Use the right tool for the right reason. And don’t reach for active-active just because it sounds hot.
(And if you &lt;em>do&lt;/em> pull it off successfully, please send me your config. I owe you a beer.)&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><media:thumbnail url="https://percona.community/blog/2025/07/jan-aa2-cover1_hu_35a1d35b5248313a.jpeg"/><media:content url="https://percona.community/blog/2025/07/jan-aa2-cover1_hu_54012d02199a39cb.jpeg" medium="image"/></item><item><title>Percona Bug Report: June 2025</title><link>https://percona.community/blog/2025/06/30/percona-bug-report-june-2025/</link><guid>https://percona.community/blog/2025/06/30/percona-bug-report-june-2025/</guid><pubDate>Mon, 30 Jun 2025 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://jira.percona.com/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs.&lt;/p>
&lt;h3 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9823" target="_blank" rel="noopener noreferrer">PS-9823&lt;/a>&lt;strong>:&lt;/strong> &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/mysql-migrate-keyring.html" target="_blank" rel="noopener noreferrer">mysql_migrate_keyring&lt;/a> fails with PS Components.&lt;/p>
&lt;p>The failure is triggered by a missing symbol, but the underlying cause is the way keyring components are built in Percona Server. When attempting to migrate keyring data (e.g., from Vault to File), the tool fails to load the Percona Server component .so files, making the migration process unusable.&lt;/p>
&lt;p>Percona Server builds a reference to the my_free symbol, which is not properly resolved in the shared libraries. In contrast, upstream MySQL builds do not include this dependency.&lt;/p>
&lt;p>This issue blocks both component-to-component and component-to-plugin keyring migrations, affecting users who rely on secure key management transitions.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.x, 8.4.x&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> Under investigation. A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9836" target="_blank" rel="noopener noreferrer">PS-9836&lt;/a>&lt;strong>:&lt;/strong> There is a regression issue with &lt;a href="https://docs.percona.com/percona-server/8.0/audit-log-filter-overview.html" target="_blank" rel="noopener noreferrer">audit_log_filter.so&lt;/a> compared to &lt;a href="https://docs.percona.com/percona-server/8.0/audit-log-plugin.html" target="_blank" rel="noopener noreferrer">audit_log.so&lt;/a>. The audit_log_filter, whether used as a plugin (8.0) or a component (8.0 and 8.4), shows a significant performance regression. When logging everything, QPS drops by over 70%. While configuring selective logging can reduce the impact, it still results in a 30–35% drop in QPS.&lt;/p>
&lt;p>For this reason, moving to audit_log_filter in 8.0 is not recommended. Additionally, this should be taken into account when planning upgrades to 8.4, as audit logging can significantly impact performance. (audit_log is not available as a component—only as a plugin.)&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.42-33, 8.4.5-5&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> Under investigation. A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9837" target="_blank" rel="noopener noreferrer">PS-9837&lt;/a>&lt;strong>:&lt;/strong> A crash occurs on replica nodes during parallel replication when an INSERT is executed on a secondary index that recently had a DELETE on the same key. The issue is caused by a race condition in the secondary index reuse logic, leading to an assertion failure (row0ins.cc:268).&lt;/p>
&lt;p>This issue is more likely to occur under &lt;strong>heavy write workloads&lt;/strong>, particularly when the application frequently performs &lt;strong>DELETE followed by INSERT on the same keys&lt;/strong>. It only affects &lt;strong>replica servers&lt;/strong> where replica_parallel_workers > 0 and slave_preserve_commit_order=ON.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.36-28, 8.0.42-33&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=118334" target="_blank" rel="noopener noreferrer">118334&lt;/a>&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> The user can modify their logic to use UPDATE instead of DELETE followed by INSERT, which avoids triggering the crash path.&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> Under investigation. A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9861" target="_blank" rel="noopener noreferrer">PS-9861&lt;/a>&lt;strong>:&lt;/strong> The audit_log_filter plugin cannot be installed when component_keyring_kmip is enabled with Fortanix DSM. While testing with component_keyring_kmip, we enabled the &lt;strong>“Allow secrets with unknown operations”&lt;/strong> option in Fortanix, which allowed the audit log installation to proceed one step further. At this point, a secret is successfully created for the audit log, but &lt;strong>MySQL crashes upon restart&lt;/strong>.&lt;/p>
&lt;p>This issue is related to &lt;strong>bug&lt;/strong> &lt;a href="https://perconadev.atlassian.net/browse/PS-9609" target="_blank" rel="noopener noreferrer">PS-9609&lt;/a> and still persists when using &lt;strong>Fortanix DSM&lt;/strong> as the KMIP server.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.42-33&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> The issue has been fixed, and the fix is expected in the upcoming release of Percona Server (PS).&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9914" target="_blank" rel="noopener noreferrer">PS-9914&lt;/a>&lt;strong>:&lt;/strong> After running ALTER TABLE … ENGINE=InnoDB to rebuild a large table (~10 million rows) with ROW_FORMAT=COMPRESSED, it was observed approximately a &lt;strong>50% drop in write-only workload throughput&lt;/strong> (measured via sysbench), despite a reduction in .ibd file size and no changes to table structure or indexes. The table had previously undergone heavy deletions (~50%), suggesting possible fragmentation prior to the rebuild.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.37-29, 8.0.42-33&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=118411" target="_blank" rel="noopener noreferrer">118411&lt;/a>&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> Under investigation. A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9956" target="_blank" rel="noopener noreferrer">PS-9956&lt;/a>&lt;strong>:&lt;/strong> PS 8.4.4-4 with group replication crashes on Oracle Linux 9 during bootstrap or failover when the audit log filter component is enabled, but does not crash on Oracle Linux 8.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.4.4-4&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> Under investigation.&lt;/p>
&lt;hr>
&lt;h3 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4652" target="_blank" rel="noopener noreferrer">PXC-4652&lt;/a>: PXC 8.4 crashes with a SIGSEGV in unordered_map called from rpl_gtid_owned during high activity, while PXC 8.0 under the same workload and data remains stable; the crash occurs randomly during operations like COMMIT or INSERT.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.4.3, 8.4.4&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 8.4.5 – Pending Release&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4684" target="_blank" rel="noopener noreferrer">PXC-4684&lt;/a>: An UPDATE query that joins two tables but modifies only one—e.g., UPDATE test.t2 JOIN test.t1 USING (i) SET t2.d = t2.d+1, t1.d = t1.d;—causes an MDL BF-BF conflict on other PXC nodes, even without triggers, as both tables are included in the Table_map_log_event.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.41&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Available&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 8.0.42 – Released | 8.4.5 – Pending Release&lt;/p>
&lt;hr>
&lt;h3 id="percona-toolkit">Percona Toolkit&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2418" target="_blank" rel="noopener noreferrer">PT-2418&lt;/a>: In &lt;strong>pt-online-schema-change 3.7.0&lt;/strong>, data was lost when executing the following SQL — the value of column col_2 was unexpectedly set to NULL:&lt;/p>
&lt;p>Eg:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ALTER TABLE t RENAME COLUMN col_1 TO col_2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MySQL version: 8.0+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pt-online-schema-change --no-version-check \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> h=127.0.0.1,u=root,p=xxx,P=xxx,D=sysbench,t=sbtest1 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --alter="RENAME COLUMN col_1 TO col_2" \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --execute --statistics&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2419" target="_blank" rel="noopener noreferrer">PT-2419&lt;/a>&lt;strong>:&lt;/strong> pt-duplicate-key-checker Ignores DESC in Index Definitions. Users running pt-duplicate-key-checker regularly observed that a newly added composite index was being incorrectly flagged as a duplicate and removed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Before:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">KEY `idx_ts` (`ts`),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">KEY `idx_ts_id` (`ts` DESC, `id`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">After:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">KEY `idx_ts_id` (`ts`)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The tool appears to ignore the &lt;strong>DESC direction&lt;/strong> in index definitions, leading to incorrect de-duplication. This behaviour may affect query plans and performance in setups relying on sort order.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2425" target="_blank" rel="noopener noreferrer">PT-2425&lt;/a>&lt;strong>:&lt;/strong> Case-Sensitive MariaDB Detection Causes Sync Failure in pt-table-sync. In pt-table-sync 3.7.0, a case-sensitive check for the MariaDB flavor ($vp->flavor() =~ m/maria/) fails because flavor() returns “MariaDB Server”, causing the condition to evaluate incorrectly. As a result, the tool looks for source_host and source_port in $source, while the actual keys are master_host and master_port, leading to failures or uninitialized value warnings.&lt;/p>
&lt;p>The Error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Use of uninitialized value in concatenation (.) or string at /usr/bin/pt-table-sync line 7086.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Manually updating the regex to m/maria/i resolves the issue. Similar case-sensitive checks appear elsewhere in the script and may require centralizing the MariaDB detection logic.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 3.7.1 - Not Yet Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2197" target="_blank" rel="noopener noreferrer">PT-2197&lt;/a>&lt;strong>:&lt;/strong> In pt-online-schema-change (version 3.7.0), attempting to run an ALTER operation results in the following error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Use of uninitialized value in string eq at /usr/bin/pt-online-schema-change line 4321&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This occurs even when replica connectivity in both directions is fully functional and multiple replicas are connected. Notably, the issue does &lt;strong>not occur in version 3.5.1&lt;/strong>, where the operation succeeds as expected (with the expected increase in connections). Schema change automation breaks unexpectedly on newer versions despite a valid replication setup.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.2, 3.6.0, 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2432" target="_blank" rel="noopener noreferrer">PT-2432&lt;/a>&lt;strong>:&lt;/strong> While pt-replica-find includes internal logic for handling replication channels, it currently lacks a corresponding --channel command-line option. Attempting to use it results in an error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pt-replica-find --channel=foo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Unknown option: channel&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This prevents users from specifying a replication channel directly, limiting the tool’s usability in multi-channel replication environments.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2448" target="_blank" rel="noopener noreferrer">PT-2448&lt;/a>&lt;strong>:&lt;/strong> pt-k8s-debug-collector should not collect secret details of pgbouncer&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2446" target="_blank" rel="noopener noreferrer">PT-2446&lt;/a>&lt;strong>:&lt;/strong> When attempting to run &lt;strong>pt-table-checksum&lt;/strong> with Group Replication enabled, and the tool returns the following error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Error checksumming table schema.table: DBD::mysql::st execute failed: The table does not comply with the requirements by an external plugin. [for Statement "DELETE FROM percona.checksums WHERE db = ? AND tbl = ?" with ParamValues: 0=' ', 1=' '] at /bin/pt-table-checksum line 11323.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It gets suspected that this is caused by the tool attempting to set @@binlog_format := ‘STATEMENT’, which is &lt;strong>not supported under Group Replication&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.7.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;h3 id="pmm-percona-monitoring-and-management">PMM [Percona Monitoring and Management]&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13994" target="_blank" rel="noopener noreferrer">PMM-13994&lt;/a>&lt;strong>:&lt;/strong> pmm_agent shows disconnected status despite active metrics collection, After a temporary connectivity issue, pmm_agent continues to display a Disconnected status in pmm-admin list, even though connectivity has been restored and dashboards are populating correctly.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ pmm-admin list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm_agent Disconnected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.43.2, 2.44.1, 3.1.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Restarting the pmm_agent should fix the issue.&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 3.4.0 - Not Yet Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13905" target="_blank" rel="noopener noreferrer">PMM-13905&lt;/a>&lt;strong>:&lt;/strong> When adding both a MongoDB Cluster and a standalone MongoDB Replica Set (not part of the cluster) to the same PMM environment (e.g., “test”), the &lt;strong>MongoDB ReplSet Summary dashboard&lt;/strong> does not allow viewing the standalone RS.&lt;/p>
&lt;p>The &lt;strong>“cluster” filter cannot be unselected&lt;/strong>, making it impossible to visualize replica sets that are not associated with a defined cluster. As a result, only RSs from the cluster are visible, while standalone RSs are excluded from the dashboard view.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.1.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Whenever possible, use &lt;strong>separate environments&lt;/strong> when adding the cluster and standalone RS nodes in PMM (e.g., use “env1” for the cluster and “env2” for the standalone RS).&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13910" target="_blank" rel="noopener noreferrer">PMM-13910&lt;/a>: In the &lt;strong>MongoDB Sharded Cluster Summary&lt;/strong> and &lt;strong>Collections&lt;/strong> dashboards, several graphs fail to populate correctly. Specifically:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Top Hottest Collections by Read&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Top Hottest Collections by Write&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>These graphs display only admin, config, and system collections, even when other collections are under heavy traffic. Additionally, the &lt;strong>Collections&lt;/strong> dashboard shows no data across all graphs—&lt;strong>except for the first one&lt;/strong> (Top 5 Databases By Size), which populates as expected.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.1.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13950" target="_blank" rel="noopener noreferrer">PMM-13950&lt;/a>&lt;strong>:&lt;/strong> In both &lt;strong>PMM 2&lt;/strong> and &lt;strong>PMM 3&lt;/strong>, with &lt;strong>MySQL 5.7&lt;/strong> and &lt;strong>MySQL 8.0&lt;/strong>, the server_uuid is not being collected from MySQL’s global variables as expected. Despite being available via SHOW GLOBAL VARIABLES LIKE ‘server_uuid’;, the PMM agent fails to parse or capture this value.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.1.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13792" target="_blank" rel="noopener noreferrer">PMM-13792&lt;/a>&lt;strong>:&lt;/strong> In PMM 2.44.0, the Advisor Insights incorrectly reports that &lt;em>journaling is not enabled&lt;/em> for MongoDB 7.0.9-15, despite journaling being enabled by default in this version.&lt;/p>
&lt;p>Attempts to explicitly enable journaling in the MongoDB config result in a startup warning:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">The storage.journal.enabled option and the corresponding --journal and --nojournal command-line options have no effect in this version... Journaling is always enabled. Please remove those options from the config.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>False alert may confuse users and lead to misconfiguration attempts that prevent MongoDB from starting.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.44, 3.1.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;h3>&lt;/h3>
&lt;h3 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-772" target="_blank" rel="noopener noreferrer">K8SPG-772&lt;/a>&lt;strong>:&lt;/strong> In the Percona PostgreSQL Operator, a runtime panic occurs when CompletedAt is nil and not properly checked before dereferencing, leading to a segmentation fault:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">panic: runtime error: invalid memory address or nil pointer dereference
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[signal SIGSEGV: segmentation violation code=0x1 addr=0x0]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Stack trace:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">github.com/percona/percona-postgresql-operator/percona/watcher.getLatestBackup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .../wal.go:123
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">github.com/percona/percona-postgresql-operator/percona/watcher.WatchCommitTimestamps
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .../wal.go:65&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The CompletedAt field is not validated before being accessed in getLatestBackup(), which causes a crash during WAL watcher execution.&lt;/p>
&lt;p>This panic can crash the operator’s goroutine, interrupting WAL monitoring and potentially affecting backup or failover logic.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.5.0, 2.6.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.7.0 - Pending Release&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-792" target="_blank" rel="noopener noreferrer">K8SPG-792&lt;/a>&lt;strong>:&lt;/strong> The upstream operator includes functionality that allows cluster or operator administrators to define default PostgreSQL images for each major version using environment variables. This enables users to create clusters without explicitly specifying spec.image, as the operator will automatically apply the predefined image.&lt;/p>
&lt;p>However, a recently introduced Patroni version check does not align with this behavior. It introduces a hardcoded dependency on spec.image, effectively bypassing the default image mechanism and undermining the intended feature.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.6.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> A possible workaround exists by manually setting the Patroni version through annotations, but this is not ideal and diminishes the convenience and flexibility originally provided.&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> A fix or workaround is expected in a future release.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1651" target="_blank" rel="noopener noreferrer">K8SPXC-1651&lt;/a>: While testing the Pod Scheduling Policy feature in &lt;a href="https://docs.percona.com/everest/index.html" target="_blank" rel="noopener noreferrer">Everest&lt;/a>, we encountered a situation where a PXC database pod remained in the &lt;strong>Pending&lt;/strong> state. This occurred because Kubernetes was unable to schedule the pod on any available node due to an affinity configuration mismatch.&lt;/p>
&lt;p>However, even after updating the affinity rules in the PerconaXtraDBCluster object, the new configuration was not propagated to the pod, and it remained in the &lt;strong>Pending&lt;/strong> state.&lt;/p>
&lt;p>The fact that the Pod remains stuck in Pending &lt;strong>even after affinity is changed or removed&lt;/strong> — and only a manual kubectl delete pod resolves it — indicates that &lt;strong>the operator fails to reconcile affinity changes properly&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> This issue affects other operators as well, not just PXC.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 1.17.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 1.20.0 - Yet to be released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1648" target="_blank" rel="noopener noreferrer">K8SPXC-1648&lt;/a>&lt;strong>:&lt;/strong> The PVC size is rounded up to the nearest whole GiB value (e.g., 1.2Gi becomes 2Gi). When a storage resize operation is triggered, the operator deletes the existing StatefulSet (STS) and recreates it with the new requested PVC size.&lt;/p>
&lt;p>However, if the new requested size rounds up to the same value as the original, the operator does not recreate the STS. Instead, it attempts to update the existing STS, which leads to the following error:&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> This issue affects other operators as well, not just PXC.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Error: failed to deploy pxc: updatePod for pxc: failed to create or update sts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">update error: StatefulSet.apps "minimal-cluster-pxc" is invalid: spec: Forbidden:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">updates to statefulset spec for fields other than 'replicas', 'ordinals',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'minReadySeconds' are forbidden&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 1.17.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 1.20.0 - Yet to be released&lt;/p>
&lt;hr>
&lt;h3 id="pbm-percona-backup-for-mongodb">PBM [Percona Backup for MongoDB]&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1499" target="_blank" rel="noopener noreferrer">PBM-1499&lt;/a>&lt;strong>:&lt;/strong> Restore to Missing Backup Fails with Unclear Error in Restore Custom Resource Status.&lt;br>
When attempting to restore from a backup that does not exist in the main storage, the operator logs correctly report the failure:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">define base backup: get backup metadata from storage: get from store: no such file&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>However, the status.error field in the PerconaServerMongoDBRestore custom resource only shows a generic message:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">error: 'define base backup: %v'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This results in a misleading or unclear error message being surfaced to the user through the custom resource, even though the logs contain the full and accurate description of the issue.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.8.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1502" target="_blank" rel="noopener noreferrer">PBM-1502&lt;/a>&lt;strong>:&lt;/strong> In &lt;strong>PBM 2.9.0&lt;/strong>, running pbm profile sync &lt;profile-name> fails with the error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Error: &lt;profile-name> or --all must be provided&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This occurs &lt;strong>even when a valid profile name is given&lt;/strong>, such as:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pbm profile sync azure-blob&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The issue affects all defined profiles (azure-blob, gcp-cs, minio) and prevents syncing individual profiles. This appears to be a bug where the CLI fails to recognize the passed argument.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.9.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Use pbm profile sync --all instead&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1538" target="_blank" rel="noopener noreferrer">PBM-1538&lt;/a>&lt;strong>:&lt;/strong> Backup is marked as successful, despite the oplog not being uploaded.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.4.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1551" target="_blank" rel="noopener noreferrer">PBM-1551&lt;/a>&lt;strong>:&lt;/strong> In a single-node PSMDB replica set with one PBM agent, PBM occasionally &lt;strong>re-executes the last issued command&lt;/strong>, causing errors like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">active lock is present&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This typically occurs when the database is under load.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.9.1&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1553" target="_blank" rel="noopener noreferrer">PBM-1553&lt;/a>&lt;strong>:&lt;/strong> When restoring a 33-shard physical backup from a mixed (MongoDB Enterprise + Percona) production cluster into a Percona-only test cluster, &lt;strong>PBM intermittently fails during the “clean-up and reset replicaset config” stage&lt;/strong>. Some shards restore successfully, while others restore only partially or fail entirely.&lt;/p>
&lt;ul>
&lt;li>Both clusters run MongoDB 6, with FCV set to 5.&lt;/li>
&lt;li>Restore uses --replset-remapping due to different replica set names&lt;/li>
&lt;li>Issue affects restores regardless of matching node count per shard.&lt;/li>
&lt;/ul>
&lt;p>The problem appears tied to the restore logic handling replica set configuration cleanup.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.9.1&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1564" target="_blank" rel="noopener noreferrer">PBM-1564&lt;/a>&lt;strong>:&lt;/strong> A user environment experiences repeated failures during &lt;strong>incremental backups&lt;/strong> with the error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ERROR: cannot use the configured storage: source backup is stored on a different storage]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Full, base incremental, and logical backups succeed without issues. There is no indication of recent storage or configuration changes, and pbm status shows backup attempts occur close together.&lt;/p>
&lt;p>The issue temporarily resolves after running pbm config --force-resync, suggesting a possible bug in storage metadata syncing or internal state handling.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.8.0&lt;br>
&lt;strong>Upstream Bug:&lt;/strong> Not Applicable&lt;br>
&lt;strong>Workaround/Fix:&lt;/strong> Not Available&lt;br>
&lt;strong>Fixed/Planned Version/s:&lt;/strong> 2.10.0 - Released&lt;/p>
&lt;hr>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;p>&lt;a href="https://jira.percona.com" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p></content:encoded><author>Aaditya Dubey</author><category>PMM</category><category>Kubernetes</category><category>MySQL</category><category>MongoDB</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/06/BugReportJune2025_hu_be31cb01591cf5d8.jpg"/><media:content url="https://percona.community/blog/2025/06/BugReportJune2025_hu_257336b252c98756.jpg" medium="image"/></item><item><title>Geeks Go Peaks: Building Community Through Adventure and Challenge</title><link>https://percona.community/blog/2025/06/25/geeks-go-peaks-building-community-through-adventure-and-challenge/</link><guid>https://percona.community/blog/2025/06/25/geeks-go-peaks-building-community-through-adventure-and-challenge/</guid><pubDate>Wed, 25 Jun 2025 00:00:00 UTC</pubDate><description>We believe this is something every strong team aspires to: taking interactions beyond the office, building a sense of community outside of work, and getting to know each other beyond job titles. That’s the kind of value people seek when they come to us.</description><content:encoded>&lt;p>We believe this is something every strong team aspires to: taking interactions beyond the office, building a sense of community outside of work, and getting to know each other beyond job titles. That’s the kind of value people seek when they come to us.&lt;/p>
&lt;p>People want to feel alive, to experience new sensations, to see the world from a different perspective - and share that journey with fellow enthusiasts.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2000" height="635" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/06/geeks-blog-1_hu_98ef1c7ef34b857.webp 768w, https://percona.community/blog/2025/06/geeks-blog-1_hu_dff1094bb2f0d7fb.webp 480w, https://percona.community/blog/2025/06/geeks-blog-1_hu_9700b106f408cda8.webp 1400w" src="https://percona.community/blog/2025/06/geeks-blog-1_hu_9700b106f408cda8.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="join-our-adventure-community-explore-connect-thrive">Join Our Adventure Community: Explore, Connect, Thrive&lt;/h2>
&lt;p>&lt;a href="https://geeksgopeaks.com/" target="_blank" rel="noopener noreferrer">The Geeks Go Peaks community&lt;/a> is all about bringing people together through unforgettable experiences. Whether you’re seeking a relaxed getaway with your partner or craving the thrill of a high-altitude expedition, we’ve got events for every adventurer. From laid-back activities like quad biking or ice dipping to group micro-adventures such as SUP boarding in central Berlin, caving, or rock climbing, there’s something for everyone. For the bold, we organize serious expeditions, including high-altitude mountain ascents. So far, we’ve summited nine peaks, each offering breathtaking views, and we’re eyeing Mount Kazbek in Georgia and Thorong Peak in Nepal next.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="952" height="635" sizes="(max-width: 952px) 100vw, 952px" srcset="https://percona.community/blog/2025/06/geeks-blog-2_hu_ef9167a89bbc606f.webp 768w, https://percona.community/blog/2025/06/geeks-blog-2_hu_241aa384d6427b8b.webp 480w, https://percona.community/blog/2025/06/geeks-blog-2_hu_def7dc61648f432d.webp 952w" src="https://percona.community/blog/2025/06/geeks-blog-2_hu_def7dc61648f432d.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="our-mission">Our mission?&lt;/h2>
&lt;p>To build a vibrant community of explorers who value shared experiences and the discovery of the world’s cultural and natural treasures. These adventures do more than just create memories - they spark lifelong friendships and expand your personal and professional networks. Through our events, people find their best friends and continue exploring the world together. It’s a joy to bring such value to the world.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1680" height="1111" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/06/geeks-blog-3_hu_66e1db0408450180.webp 768w, https://percona.community/blog/2025/06/geeks-blog-3_hu_23f552803595d71b.webp 480w, https://percona.community/blog/2025/06/geeks-blog-3_hu_a2d38e41478294d4.webp 1400w" src="https://percona.community/blog/2025/06/geeks-blog-3_hu_a2d38e41478294d4.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="why-the-it-community">Why the IT Community?&lt;/h2>
&lt;p>The IT industry is full of driven individuals who spend long hours behind screens. At some point, many realize they need more - more movement, more nature, more challenges. Outdoor activities like running, climbing, obstacle races, diving, or windsurfing not only boost physical health but also recharge mental well-being. IT professionals are often high-achievers, eager to push their limits, whether that’s training for a marathon or summiting iconic peaks. We connect these like-minded adventurers, fostering a community where they can inspire and support each other.&lt;/p>
&lt;blockquote>
&lt;p>“You can’t beat shared suffering as team building activity” – Peter Zaitsev (Founder at Percona, Altinity, FerretDB, Coroot Database)&lt;/p>&lt;/blockquote>
&lt;p>
&lt;figure>&lt;img width="1680" height="1260" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/06/geeks-blog-4_hu_e23a936cc8aaffb0.webp 768w, https://percona.community/blog/2025/06/geeks-blog-4_hu_1679f9cce8438a5f.webp 480w, https://percona.community/blog/2025/06/geeks-blog-4_hu_83ceb4e36b9c0114.webp 1400w" src="https://percona.community/blog/2025/06/geeks-blog-4_hu_83ceb4e36b9c0114.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="what-weve-done">What We’ve Done&lt;/h2>
&lt;p>Our past events showcase the diversity of our adventures:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Aconcagua, Argentina: A grueling climb to nearly 7,000 meters rewarded us with jaw-dropping views and a profound sense of achievement. “Aconcagua was the trip of a lifetime. I’d recommend it to anyone in good shape and hungry for a challenge. You won’t regret it!”&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Caving in Budapest: An underground world surprised even locals, proving adventure is often closer than you think. “I’m from Hungary and never explored the caves before. Doing it with GGP was the perfect way to finally change that - I absolutely loved it!”&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Rock Climbing in Red Rock Canyon, Nevada: First-timers conquered the walls, forging bonds while cheering each other on. “I couldn’t have asked for a better first climbing adventure. The group, the views - everything was awesome.”&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Northern Lights in Lapland: Snowy forests and vibrant skies created magical nights and dreams of future trips. “This wasn’t my first GGP trip - we’re practically family now. It was magic seeing the lights every single day.”&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Via Ferrata in the Dolomites, Italy; a descent through Vallée Blanche in France; local events with paddleboarding and rock climbing in North Carolina… and that’s just the beginning. We already have so much planned for this year and the next!&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="800" height="533" sizes="(max-width: 800px) 100vw, 800px" srcset="https://percona.community/blog/2025/06/geeks-blog-5_hu_a980d8467bc4a216.webp 768w, https://percona.community/blog/2025/06/geeks-blog-5_hu_d8bf74945cfc2791.webp 480w, https://percona.community/blog/2025/06/geeks-blog-5_hu_d50062e121e619ef.webp 800w" src="https://percona.community/blog/2025/06/geeks-blog-5_hu_d50062e121e619ef.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="upcoming-adventures">Upcoming Adventures&lt;/h2>
&lt;p>Our calendar is brimming with thrilling plans. In August, we’ll tackle the exhilarating ascent of Mount Kazbek. Then we’re off to hike under the midnight sun in &lt;a href="https://geeksgopeaks.com/midnight_sun_hike_longyearbyen_august_2025" target="_blank" rel="noopener noreferrer">Svalbard&lt;/a>, an awe-inspiring land where untouched Arctic nature reigns and polar bears outnumber people.&lt;/p>
&lt;p>We’ll ride the wild waters of&lt;a href="https://geeksgopeaks.com/gully_river_rafting_rafting_the_dam_release" target="_blank" rel="noopener noreferrer"> the Gully River&lt;/a> during dam release season, set sail on catamarans through the British Virgin Islands in February, and explore wintertime Alaska with freeride skiing, glacier ice climbing, and evenings in a Nordic sauna.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="506" height="675" sizes="(max-width: 506px) 100vw, 506px" srcset="https://percona.community/blog/2025/06/geeks-blog-6_hu_9caf66fdafce4372.webp 480w, https://percona.community/blog/2025/06/geeks-blog-6_hu_4ff3d5cc54eab629.webp 506w" src="https://percona.community/blog/2025/06/geeks-blog-6_hu_4ff3d5cc54eab629.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And that’s just the beginning - Nepal, Bolivia, the Grand Canyon, the volcanoes of Vanuatu, Peru, and so much more are waiting.&lt;/p>
&lt;p>For those in the USA, we’ve got local adventures like Grand Canyon Rim-to-Rim hikes, quick one- and two-day hikes, paddleboarding and kayaking, cycling, fishing and yachting, rock climbing, and even diving. And events tied to conferences such as Re:Invent, ATO Raleigh, All Things Open RTP, and Mountain Data &amp; Dev Conference in Utah.&lt;/p>
&lt;p>And this fall, on September 21, we’re hosting a women’s charity run - &lt;a href="https://geeksgopeaks.com/codepinkrun2025" target="_blank" rel="noopener noreferrer">CODE PINK RUN&lt;/a>! All proceeds will go to the Pretty In Pink Foundation to support women in their fight against breast cancer. Join us! We’d love to see everyone at this celebration of female solidarity!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="900" height="1200" sizes="(max-width: 900px) 100vw, 900px" srcset="https://percona.community/blog/2025/06/geeks-blog-7_hu_93cde0bf0ff1fa3b.webp 768w, https://percona.community/blog/2025/06/geeks-blog-7_hu_8fc01ea0af7b2723.webp 480w, https://percona.community/blog/2025/06/geeks-blog-7_hu_93921cddac06f269.webp 900w" src="https://percona.community/blog/2025/06/geeks-blog-7_hu_93921cddac06f269.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="stay-connected">Stay Connected&lt;/h2>
&lt;p>We keep our community engaged via newsletters, &lt;a href="https://www.meetup.com/geeks-go-peaks-club/" target="_blank" rel="noopener noreferrer">Meetup&lt;/a>, &lt;a href="https://geeksgopeaks.slack.com/join/shared_invite/zt-35ofu5wqo-MQLEe6TZyc6JMGUTkfIl6A#" target="_blank" rel="noopener noreferrer">Slack&lt;/a>, &lt;a href="http://t.me/geeksgopeaks" target="_blank" rel="noopener noreferrer">Telegram&lt;/a>, and &lt;a href="https://www.strava.com/clubs/geeksgopeaks" target="_blank" rel="noopener noreferrer">Strava&lt;/a> - where we train together.. Our Slack platform lets members discuss training plans, gear, and expedition prep, making us more than just a club, but a true community united by shared passions.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1099" height="879" sizes="(max-width: 1099px) 100vw, 1099px" srcset="https://percona.community/blog/2025/06/geeks-blog-8_hu_1f067563eaf0ab66.webp 768w, https://percona.community/blog/2025/06/geeks-blog-8_hu_f0db2cad98c7a99f.webp 480w, https://percona.community/blog/2025/06/geeks-blog-8_hu_f91523ffa80f812.webp 1099w" src="https://percona.community/blog/2025/06/geeks-blog-8_hu_f91523ffa80f812.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Anyone can suggest an adventure they’d like to be part of! We’re always excited about exploring new countries and trying new activities, sporty or not. Our goal is to help people find like-minded adventurers to reach new heights and share unforgettable experiences together!&lt;/p>
&lt;p>Join us to explore the world, meet incredible people, and create stories you’ll tell for years.&lt;/p>
&lt;p>Let’s adventure together!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1319" height="879" sizes="(max-width: 1319px) 100vw, 1319px" srcset="https://percona.community/blog/2025/06/geeks-blog-9_hu_589249861bfe424f.webp 768w, https://percona.community/blog/2025/06/geeks-blog-9_hu_832cf7dcbee4fbe2.webp 480w, https://percona.community/blog/2025/06/geeks-blog-9_hu_a609822cf4fdadcf.webp 1319w" src="https://percona.community/blog/2025/06/geeks-blog-9_hu_a609822cf4fdadcf.webp" alt="Geeks Go Peaks" loading="lazy" />&lt;/figure>&lt;/p></content:encoded><author>Iuliia (Julie) Slobodian</author><category>GeeksGoPeaks</category><category>Adventure</category><media:thumbnail url="https://percona.community/blog/2025/06/geeks-go-cover_hu_ba57ab57e229756a.jpg"/><media:content url="https://percona.community/blog/2025/06/geeks-go-cover_hu_471287009f332ef0.jpg" medium="image"/></item><item><title>PostgreSQL active-active replication, do you really need it?</title><link>https://percona.community/blog/2025/06/18/postgresql-active-active-replication-do-you-really-need-it/</link><guid>https://percona.community/blog/2025/06/18/postgresql-active-active-replication-do-you-really-need-it/</guid><pubDate>Wed, 18 Jun 2025 00:00:00 UTC</pubDate><description>Before we start, what is active-active? Active-active, also referred to as multi-primary, is a setup where multiple database nodes can accept writes at the same time and propagate those changes to the others. In comparison, regular streaming replication in PostgreSQL allows only one node (the primary) to accept writes. All other nodes (replicas) are read-only and follow changes.</description><content:encoded>&lt;h2 id="before-we-start-what-is-active-active">Before we start, what is active-active?&lt;/h2>
&lt;p>&lt;strong>Active-active&lt;/strong>, also referred to as &lt;strong>multi-primary&lt;/strong>, is a setup where multiple database nodes can accept writes at the same time and propagate those changes to the others. In comparison, regular streaming replication in PostgreSQL allows only one node (the primary) to accept writes. All other nodes (replicas) are read-only and follow changes.&lt;/p>
&lt;p>In an active-active setup:&lt;/p>
&lt;ul>
&lt;li>There is no single point of write.&lt;/li>
&lt;li>Applications can write to any node.&lt;/li>
&lt;li>The database needs a way to sort out conflicts when two nodes try to concurrently change the same data.&lt;/li>
&lt;/ul>
&lt;p>That last point is the hardest one. PostgreSQL was not designed for concurrent writes from multiple nodes; it’s not a distributed database and does not leverage proprietary dedicated storage capabilities. So, every multi-primary implementation has to solve the issue of conflicting concurrent writes somehow. Some resolve conflicts using timestamps or priorities. Some push conflict resolution to the application. Some avoid it altogether by writing to separate subsets of data.&lt;/p>
&lt;p>While simple in concept, implementing an active-active configuration is challenging.&lt;/p>
&lt;h2 id="pgactive-to-the-rescue">pgactive to the rescue?&lt;/h2>
&lt;p>Last week, Amazon &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/06/open-sourcing-pgactive-active-active-replication-extension-postgresql/" target="_blank" rel="noopener noreferrer">open-sourced its active-active replication extension&lt;/a>, pgactive (&lt;a href="https://github.com/aws/pgactive" target="_blank" rel="noopener noreferrer">https://github.com/aws/pgactive&lt;/a>). While &lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/10/pgactive-active-active-replication-extension-postgre-sql-amazon-rds/" target="_blank" rel="noopener noreferrer">the extension has been generally available on AWS RDS since October 2023&lt;/a>, there are unfortunately not many stories about it being used in production available. To be fair, I was not able to find any 😟&lt;/p>
&lt;p>We often see both users and customers come asking for active-active or multi-master. These terms, while different, are so often used as synonyms that we’ve come to expect that. So, though I understand that every multi-master is active-active but not necessarily the other way around, for the sake of clarity, if I use one or the other term throughout this post, they will refer to the same concept.&lt;/p>
&lt;p>As it is an open-source extension now, it immediately raised my interest. It seems that it could cover this ask from users I often speak with about their pains and needs. As a product manager, when I hear an ask, I always try to understand the reasons—whether it is a requirement, a need, or actually a solution that addresses one. For multi-master, my strong opinion is that it is a solution.&lt;/p>
&lt;h2 id="key-question-do-you-need-it">Key question: do you need it?&lt;/h2>
&lt;p>I like the opening of &lt;a href="https://www.youtube.com/watch?v=Es9ZNbgVUsc" target="_blank" rel="noopener noreferrer">the talk Johnathan Katz gave on PGConf Europe 2023 in Prague&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>The first thing I always say on the journey to active active is: do you really need it? Because it definitely solves a lot of problems (…) but it’s very hard to manage.&lt;/p>&lt;/blockquote>
&lt;p>That is exactly the first question I ask when I hear someone asking for active-active. We have seen teams introduce active-active replication for the wrong reasons. Here I have to pause. Yes, as database experts, we have strong opinions about what are the right reasons for using multi-master. It’s not a silver bullet. It’s not “cool infra.” And using it without a good reason tends to hurt for a long, long time.&lt;/p>
&lt;p>So, what are the reasons to use active-active? I do not claim to be able to cover all scenarios, but I hope this post raises enough eyebrows and sparks enough discussion to eventually have solid reading material for anyone considering active-active that will help them make an informed decision.&lt;/p>
&lt;h2 id="what-are-good-reasons">What are “good” reasons?&lt;/h2>
&lt;p>These are some of the situations where active-active might actually make sense. While there may be more, here’s my top 5:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Business continuity across regions: extreme HA needs (99.999% uptime)&lt;/strong>&lt;/p>
&lt;p>Just to remind everyone what 5 nines mean, I will refer you to &lt;a href="https://x.com/BenjDicken/status/1925946372034802097" target="_blank" rel="noopener noreferrer">this message&lt;/a>:&lt;/p>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">uptime → max monthly downtime:&lt;br>&lt;br>99% → 7.3 hours&lt;br>99.9% → 44 minutes&lt;br>99.99% → 4 minutes&lt;br>99.999% → 26 seconds&lt;br>99.9999% → 3 seconds&lt;br>99.99999% → 1/4 second&lt;br>&lt;br>where do you land?&lt;br>how much time/money would you invest to add a 9?&lt;/p>— Ben Dicken (@BenjDicken) &lt;a href="https://twitter.com/BenjDicken/status/1925946372034802097?ref_src=twsrc%5Etfw">May 23, 2025&lt;/a>&lt;/blockquote> &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8">&lt;/script>
&lt;p>26 seconds of downtime a month, that’s 312 seconds a year. Yes, 5.2 minutes a year.&lt;/p>
&lt;p>Now think about the cost of delivering that sort of reliability. I find &lt;a href="https://en.wikipedia.org/wiki/High_availability" target="_blank" rel="noopener noreferrer">this Wikipedia page&lt;/a> surprisingly helpful in conveying how little time for maintenance and failures is left with enough nines added.&lt;/p>
&lt;p>Consider what it would take to absorb failures across data centers or cloud regions without rejecting writes or failing over manually. Active-active can help here because failover becomes instant and transparent; write traffic just shifts to surviving nodes.&lt;/p>
&lt;p>But again, the cost will match the ambition. Do you plan HA within the same server room with separate power and networking? Or are you aiming for full geographic separation, to stay online even during a &lt;a href="https://euromed-economists.org/countrywide-power-outages-across-spain-and-portugal-what-happened-and-why/#gsc.tab=0" target="_blank" rel="noopener noreferrer">country-wide outage&lt;/a>? These decisions massively influence the architecture, and together with your uptime goals, they define the cost. At this level, every part of the solution should reflect real business needs, because every layer of complexity adds expense. You can’t overstate the value of planning and proper analysis when building systems like this.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Write availability during regional failures&lt;/strong>&lt;/p>
&lt;p>If your business serves a global customer base and absolutely must accept writes in more than one region, for example, to maintain uptime guarantees or continue operating during a regional outage, then active-active might be the least painful of the painful options.&lt;/p>
&lt;p>This is not about low latency. This is about keeping write traffic flowing even when something breaks. That includes:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Cloud infrastructure outages&lt;/strong>, such as full region loss or core service failure from your cloud provider:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.crn.com/news/cloud/aws-outage-downs-other-websites-apps" target="_blank" rel="noopener noreferrer">AWS us-east-1 outage in June 2023&lt;/a> affected 104 services. A &lt;a href="https://assets-global.website-files.com/64b69422439318309c9f1e44/6554bcf27d66c0c9135d3509_Parametrix%20Insurance-%20Cloud%20Outage%20and%20the%20Fortune%20500%202023.pdf" target="_blank" rel="noopener noreferrer">Parametrix Insurance report&lt;/a> estimated a 24-hour outage in this region could lead to $3.4 billion in direct revenue loss.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://apnews.com/article/18ad53dca0385a83ca5a4e219bcb3a9d" target="_blank" rel="noopener noreferrer">Google Cloud outage in June 2025&lt;/a> impacted Spotify, YouTube, Twitch, and others.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://aws.amazon.com/message/41926/" target="_blank" rel="noopener noreferrer">AWS S3 outage in 2017&lt;/a> was caused by an internal mistake and &lt;a href="https://www.theregister.com/2017/03/01/aws_s3_outage/" target="_blank" rel="noopener noreferrer">disrupted GitHub, Slack, and more&lt;/a>.&lt;/p>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">Joys of the &lt;a href="https://twitter.com/internetofshit?ref_src=twsrc%5Etfw">@internetofshit&lt;/a> - AWS goes down. So does my TV remote, my light controller, even my front gate. Yay for 2017.&lt;/p>— Brian (@Hamster_Brian) &lt;a href="https://twitter.com/Hamster_Brian/status/836666914344611841?ref_src=twsrc%5Etfw">February 28, 2017&lt;/a>&lt;/blockquote> &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8">&lt;/script>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Name resolution and routing issues&lt;/strong>, such as DNS or BGP failures that take your services offline even when your backend is healthy:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.thedailybeast.com/massive-internet-outage-disrupts-services-for-google-amazon-and-more" target="_blank" rel="noopener noreferrer">Dyn DNS attack in 2016&lt;/a> brought down Twitter, Reddit, and Spotify.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.cloudflare.com/october-2021-facebook-outage/" target="_blank" rel="noopener noreferrer">Facebook DNS and BGP misconfiguration in 2021&lt;/a> made their domains unreachable and left millions of users in the dark.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>That day, Twitter (rest in peace) &lt;a href="https://www.aljazeera.com/news/2021/10/5/hello-everyone-twitter-pokes-fun-at-facebook-owned-app-outage" target="_blank" rel="noopener noreferrer">greeted the internet&lt;/a>:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="542" height="988" sizes="(max-width: 542px) 100vw, 542px" srcset="https://percona.community/blog/2025/06/twitter1_hu_355a80d213fc77dd.webp 480w, https://percona.community/blog/2025/06/twitter1_hu_a67da832824f232c.webp 542w" src="https://percona.community/blog/2025/06/twitter1_hu_a67da832824f232c.webp" alt="Twitter" loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>All jokes aside, these are serious risks. If this kind of failure is unacceptable for your business, and you are willing to take on the operational weight and cost (we will get to that), active-active may be the right tool.&lt;/p>
&lt;p>But be honest about what you are solving. If your system demands strong consistency, every transaction still needs coordination across nodes. For example, if a user in Australia writes to a local node, and the other node is in the United States, that write still involves a round trip to the United States before it can commit. That round trip adds latency, not removes it. While it may be 150-200ms on average for the Australia to USA round trip, it adds up with volume.&lt;/p>
&lt;p>The real benefit of active-active here is not performance. It is write availability during failure. If your business cannot afford to reject writes when a region goes dark, and you are prepared for everything else that comes with this decision, this might be one of the rare cases where active-active makes sense.&lt;/p>
&lt;p>Just be clear, what you are solving here is not distributed latency, but write continuity when something fails.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Migrating legacy architectures&lt;/strong>&lt;/p>
&lt;p>If you’re part of an organization moving away from systems like Oracle RAC or GoldenGate, where distributed write semantics were either built-in or at least promised, you may face business or political pressure to deliver “the same thing” on PostgreSQL.&lt;/p>
&lt;p>In these cases, active-active might be the shortest path to satisfying the checkbox. But it’s almost always a transitional compromise, not the destination. As any compromise, that’s not going to be all pleasant. The technically better (but less politically correct) move is usually to re-architect for clearer ownership of writes and better separation of concerns.&lt;/p>
&lt;p>If you can push for that path, do it. If not, be aware of the cost you’re inheriting.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Application performance (not database performance)&lt;/strong>&lt;/p>
&lt;p>In the end, what you are really trying to improve is not the database throughput, but the end-user experience. Active-active may be worth considering not for improving database internals, but for reducing perceived latency in globally distributed apps or smoothing responsiveness during network transitions.&lt;/p>
&lt;p>In rare cases, this might justify active-active if the application can route users to their nearest region and issue local writes. But your app must be built for it. Deterministic conflict handling, &lt;a href="https://serverlessland.com/event-driven-architecture/idempotency" target="_blank" rel="noopener noreferrer">idempotency&lt;/a>, and careful session management are must-haves in such a case.&lt;/p>
&lt;p>If your database is fast, but the user still feels lag because the write travels halfway across the planet, active-active might help. But this should be a last resort, not a default choice.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Local HA in disconnected or semi-connected environments&lt;/strong>&lt;/p>
&lt;p>In edge computing, retail stores, ships, or military use cases, you might want each node to function independently to address intermittent connectivity. In such scenarios, you will still be able to write locally when the network is not available. When the network comes back, the changes are going to be synced. While conflict avoidance may be the strategy you go for, in the end, it’s going to become a cost of conflict resolution.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="whats-next">What’s next?&lt;/h2>
&lt;p>In the next blog post I will focus on the bad reasons to consider active-active replication and on the cost that should not be forgotten. Stay tuned!&lt;/p></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><media:thumbnail url="https://percona.community/blog/2025/06/jan-aa1-cover1_hu_d7fdd23aca387868.jpeg"/><media:content url="https://percona.community/blog/2025/06/jan-aa1-cover1_hu_d23dad488423716c.jpeg" medium="image"/></item><item><title>What's new in PMM 3.2.0: Five major improvements you need to know</title><link>https://percona.community/blog/2025/06/03/percona-monitoring-management-3-2-five-improvements/</link><guid>https://percona.community/blog/2025/06/03/percona-monitoring-management-3-2-five-improvements/</guid><pubDate>Tue, 03 Jun 2025 00:00:00 UTC</pubDate><description>PMM 3.2.0 brings some long-awaited fixes and new capabilities. You can now install PMM Client on Amazon Linux 2023 with proper RPM packages, get complete MySQL 8.4 replication monitoring, and track MongoDB backups directly in PMM.</description><content:encoded>&lt;p>PMM 3.2.0 brings some long-awaited fixes and new capabilities. You can now install PMM Client on Amazon Linux 2023 with proper RPM packages, get complete MySQL 8.4 replication monitoring, and track MongoDB backups directly in PMM.&lt;/p>
&lt;p>Here’s what’s most important in this release:&lt;/p>
&lt;h2 id="1-native-amazon-linux-2023-support---no-more-workarounds">1. Native Amazon Linux 2023 support - no more workarounds&lt;/h2>
&lt;p>What’s new: If you’ve been running PMM Client on AL2023 and dealing with complex manual installations, those days are over. You can now install PMM Client through &lt;a href="https://repo.percona.com" target="_blank" rel="noopener noreferrer">native RPM packages&lt;/a> just like any other supported platform.&lt;/p>
&lt;p>What this means for you: Streamlined setup means you can get your Amazon Linux 2023 environments monitored faster.&lt;/p>
&lt;h2 id="2-complete-mysql-84-replication-monitoring">2. Complete MySQL 8.4 replication monitoring&lt;/h2>
&lt;p>What’s new: PMM now fully supports replication monitoring for MySQL 8.4, including key metrics like IO Thread status, SQL Thread status, and Replication Lag. MySQL 8.4 changed how these metrics are exposed, and earlier PMM versions couldn’t track them accurately.&lt;/p>
&lt;p>What this means for you: With the upgraded MySQL Exporter (v0.17.2), you now get complete replication monitoring across all supported MySQL versions (5.7, 8.0, and 8.4) without any visibility gaps.&lt;/p>
&lt;h2 id="3-mongodb-backup-monitoring-dashboard">3. MongoDB backup monitoring dashboard&lt;/h2>
&lt;p>What’s new: The new &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/reference/dashboards/dashboard-mongodb-PBM-details.html" target="_blank" rel="noopener noreferrer">PBM Details dashboard &lt;/a>lets you monitor MongoDB backups directly in PMM using the PBM collector. Instead of switching between PMM and separate backup tools, you now get a real-time, unified view of backup activity across replica sets and sharded clusters.&lt;/p>
&lt;p>What this means for you: Easily track backup status, configuration, size, duration, PITR status, and recent successful backups—all in one place. No more tool-hopping to stay on top of your backup operations.&lt;/p>
&lt;h2 id="4-grafana-116-upgrade-with-enhanced-capabilities">4. Grafana 11.6 upgrade with enhanced capabilities&lt;/h2>
&lt;p>What’s new: PMM now ships with Grafana 11.6, delivering enhanced visualization capabilities and improved alerting workflows.&lt;/p>
&lt;p>Key features include:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Alert state history for reviewing historical changes in alert statuses&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Improved panel features and visualization actions&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Simplified alert creation with better UI workflows&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Recording rules for creating pre-computed metrics&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Navigation bookmarks for quick dashboard access&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>What this means for you: These enhancements make your monitoring dashboards more interactive, your alerting more sophisticated, and your overall monitoring workflow more efficient.&lt;/p>
&lt;h2 id="5-dramatically-improved-query-analytics-performance">5. Dramatically improved Query Analytics performance&lt;/h2>
&lt;p>What’s new: We’ve optimized QAN filter loading performance to reduce the number of processed rows by up to 95% in large environments.&lt;/p>
&lt;p>What this means for you: Filters on the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/use/qan/index.html?h=query+ana" target="_blank" rel="noopener noreferrer">PMM Query Analytics page&lt;/a> now load much faster, making the interface more responsive to improve your troubleshooting efficiency.&lt;/p>
&lt;h2 id="additional-improvements-worth-noting">Additional improvements worth noting&lt;/h2>
&lt;p>Beyond these five major enhancements, PMM 3.2.0 also introduces:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Secure ClickHouse connections with authenticated credential support&lt;/p>
&lt;/li>
&lt;li>
&lt;p>MongoDB Feature Compatibility Version (FCV) panels for better cluster version visibility&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Nomad integration laying groundwork for future extensibility&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Numerous bug fixes improving stability across ProxySQL, PostgreSQL, and MySQL monitoring&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="getting-started-with-pmm-320">Getting started with PMM 3.2.0&lt;/h2>
&lt;p>Ready to experience these improvements? Set up your PMM 3.2.0 instance using our &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/quickstart/quickstart.html" target="_blank" rel="noopener noreferrer">quickstart guide&lt;/a> or upgrade your existing installation following our &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/pmm-upgrade/migrating_from_pmm_2.html" target="_blank" rel="noopener noreferrer">migration documentation&lt;/a>.&lt;/p>
&lt;p>For existing users with external PostgreSQL databases, make sure to review the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/pmm-upgrade/external_postgres_pmm_upgrade.html" target="_blank" rel="noopener noreferrer">external PostgreSQL configuration migration guide&lt;/a> before upgrading.&lt;/p>
&lt;p>Questions or feedback? We’d love to hear from you! Connect with the Percona community through our &lt;a href="https://forums.percona.com/c/percona-monitoring-and-management-pmm/30/none" target="_blank" rel="noopener noreferrer">forums&lt;/a> or join the conversation on our community channels.&lt;/p></content:encoded><author>Catalina Adam</author><category>PMM</category><category>Monitoring</category><category>Percona</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2025/06/PMM-32-five_hu_b44082c6b85bf6ef.jpg"/><media:content url="https://percona.community/blog/2025/06/PMM-32-five_hu_9306982df4fa628.jpg" medium="image"/></item><item><title>PostgreSQL 18 - Top Enterprise Features (fast read)</title><link>https://percona.community/blog/2025/05/26/postgresql-18-top-enterprise-features-fast-read/</link><guid>https://percona.community/blog/2025/05/26/postgresql-18-top-enterprise-features-fast-read/</guid><pubDate>Mon, 26 May 2025 00:00:00 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="2042" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/05/pg18img_hu_2213b29ac0a7d2d3.webp 768w, https://percona.community/blog/2025/05/pg18img_hu_83d583230f411b9b.webp 480w, https://percona.community/blog/2025/05/pg18img_hu_7ece06f286635482.webp 1400w" src="https://percona.community/blog/2025/05/pg18img_hu_7ece06f286635482.webp" alt="Postgres 18 is coming!" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>So the &lt;a href="https://www.postgresql.org/about/news/postgresql-18-beta-1-released-3070/" target="_blank" rel="noopener noreferrer">Beta1 is available for PostgreSQL 18&lt;/a> and while not all the &lt;a href="https://www.postgresql.org/docs/18/release-18.html" target="_blank" rel="noopener noreferrer">features&lt;/a> have to make it to GA, we can surely hope they do!&lt;/p>
&lt;p>Taking a close look at &lt;a href="https://www.postgresql.org/docs/18/release-18.html" target="_blank" rel="noopener noreferrer">what’s coming&lt;/a>, here below is the selection of what excites me in particular:&lt;/p>
&lt;h2 id="1-oauth-20-authentication-support">1. OAuth 2.0 authentication support&lt;/h2>
&lt;p>→ Finally aligns with modern enterprise SSO and identity standards (e.g., Okta, Azure AD). A major win for security teams and regulatory compliance.&lt;/p>
&lt;h2 id="2-logical-replication-from-standbys-now-with-conflict-logging">2. Logical replication from standbys now with conflict logging&lt;/h2>
&lt;p>→ Now you can replicate from replicas not only primary nodes and thanks to conflict logging troubleshooting issues moves closer to what the users have been asking for. It’s a big step toward robust, native, HA-friendly logical replication. Not yet there, but on the right path!&lt;/p>
&lt;h2 id="3-asynchronous-io-aio">3. Asynchronous I/O (AIO)&lt;/h2>
&lt;p>→ Modern async reads improve performance, especially under heavy parallel workloads. Foundation for future IO improvements, and also a feature that scratches an itch for a lot of cloud deployments.&lt;/p>
&lt;h2 id="4-faster--safer-major-upgrades">4. Faster &amp; safer major upgrades&lt;/h2>
&lt;p>→ &lt;code>pg_upgrade&lt;/code> enhancements like parallel upgrade checks (&lt;code>--jobs&lt;/code>), safer upgrades (&lt;code>--swap&lt;/code>), and planner stats carried forward = faster version adoption and smoother upgrades for large clusters.&lt;/p>
&lt;h2 id="5-observability">5. Observability++&lt;/h2>
&lt;p>→ Enhanced EXPLAIN statement and pg_stat_io improvements enhance understanding and optimize I/O behavior across tables, indexes, and WAL. This reduces the need for external monitoring tools.&lt;/p>
&lt;h2 id="other-notable-features">Other Notable Features&lt;/h2>
&lt;p>While these are the top mentions, the goodies are not limited to only these. Some smaller improvements are just as exciting. Looking at these, the top interesting ones for me are:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>New&lt;/strong> &lt;code>extension_control_path&lt;/code> &lt;strong>Server Variable&lt;/strong>&lt;/p>
&lt;p>→ Enables operators to manage PostgreSQL extensions via &lt;strong>Kubernetes image volumes&lt;/strong> (&lt;a href="https://kubernetes.io/blog/2025/04/29/kubernetes-v1-33-image-volume-beta/" target="_blank" rel="noopener noreferrer">Kubernetes 1.33 image volumes&lt;/a>) without modifying the base image.&lt;/p>
&lt;p>→ Huge win for immutable image strategies and GitOps-friendly operator design.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Easier online constraints management&lt;/p>
&lt;p>→ Add new &lt;code>NOT NULL&lt;/code> constraints without locking large tables using &lt;code>NOT VALID&lt;/code>&lt;/p>
&lt;p>→ Use &lt;code>NOT ENFORCED&lt;/code> foreign keys and &lt;code>CHECK&lt;/code> to model relationships without runtime overhead&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Smarter index maintenance (bottom-up deletion)&lt;/strong>&lt;/p>
&lt;p>→ Reduces bloat, lowers vacuum overhead.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>SQL/JSON path support + JSON performance gains&lt;/strong>&lt;/p>
&lt;p>→ Enables document-style querying at scale.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Better insights into queries and vacuum&lt;/strong>&lt;/p>
&lt;p>→ &lt;code>EXPLAIN&lt;/code> now includes buffer usage in subplans, triggers, and functions which helps spot slow parts&lt;/p>
&lt;p>→ &lt;code>pg_stat_all_tables&lt;/code> now tracks how much time vacuum and autovacuum take per table&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Indexing improvements&lt;/strong>&lt;/p>
&lt;p>→ Parallel GIN builds help speed up full-text and vector searches key for hybrid full-text and vector search&lt;/p>
&lt;p>→ B-tree skip scans make range and selective queries faster&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="what-enterprises-would-want-in-postgresql-19">What enterprises would want in PostgreSQL 19+&lt;/h2>
&lt;p>There is already a lot to like in this release, but based on what we hear from users, customers, and our own teams, here is what is still high on the list.
First up and this one’s close to home is that we’d love to see the Transparent Data Encryption (TDE) patches from Percona Server for PostgreSQL make their way upstream. That would allow users to benefit from &lt;code>pg_tde&lt;/code> directly in Community PostgreSQL Server.
The rest of the list is a mix of long standing asks and forward looking ideas. It is a wishlist for sure, but one we hope to help make real over time:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Built-in Logical Conflict Resolution Algorithms&lt;/strong>&lt;/p>
&lt;p>→ Support for conflict-handling strategies (e.g., last-write-wins, column-level rules) would simplify bidirectional replication and eliminate the need for custom frameworks, opening the door for fully open-source multi-master setups.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Logical failover orchestration&lt;/strong>&lt;/p>
&lt;p>→ Seamless promotion and failover in logical topologies, with less reliance on external tooling. This would be great from the perspective of both Kubernetes deployments as well as the ease of use for HA solutions out there.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Better integration with external auth systems&lt;/strong>&lt;/p>
&lt;p>→ Automatic PostgreSQL user creation based on OAUTH/LDAP roles at login, reducing operational burden for large-scale identity management and central access control.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pluggable or columnar storage support&lt;/strong>&lt;/p>
&lt;p>→ Native support or better extension hooks for OLAP and hybrid workloads, closing the gap with cloud-native alternatives like Citus or Redshift.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Sharding to provide horizontal scaling&lt;/strong>&lt;/p>
&lt;p>→ Transparent sharding is a highly desired capability that becomes critical as workloads scale. While not always needed on day one, having built-in sharding means teams can grow without reinventing the wheel. Lack of it makes horizontal scaling complex, requiring more expertise and introducing higher operational overhead for DBA teams.&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Jan Wieremjewicz</author><category>PostgreSQL</category><category>Opensource</category><category>pg_jan</category><media:thumbnail url="https://percona.community/blog/2025/05/jan-pg-18-cover_hu_d632612eb3789ade.jpg"/><media:content url="https://percona.community/blog/2025/05/jan-pg-18-cover_hu_a35314ad552524c4.jpg" medium="image"/></item><item><title>OS Platform End of Life (EOL) Announcement for Ubuntu 20.04 LTS</title><link>https://percona.community/blog/2025/05/22/os-platform-end-of-life-eol-announcement-for-ubuntu-20.04-lts/</link><guid>https://percona.community/blog/2025/05/22/os-platform-end-of-life-eol-announcement-for-ubuntu-20.04-lts/</guid><pubDate>Thu, 22 May 2025 00:00:00 UTC</pubDate><description>Ubuntu 20.04 LTS (Focal Fossa) is scheduled to reach its official end of life on May 31, 2025. In alignment with the upstream vendor’s lifecycle, we are also ending platform support for Ubuntu 20.04 for all our MySQL related product offerings. This date and others are published in advance on our Percona Release Lifecycle Overview page.</description><content:encoded>&lt;p>&lt;a href="https://ubuntu.com/blog/ubuntu-20-04-lts-end-of-life-standard-support-is-coming-to-an-end-heres-how-to-prepare" target="_blank" rel="noopener noreferrer">Ubuntu 20.04 LTS&lt;/a> (Focal Fossa) is scheduled to reach its official end of life on &lt;strong>May 31, 2025&lt;/strong>. In alignment with the upstream vendor’s lifecycle, we are also ending platform support for Ubuntu 20.04 for all our MySQL related product offerings. This date and others are published in advance on our &lt;a href="https://www.percona.com/services/policies/percona-software-support-lifecycle" target="_blank" rel="noopener noreferrer">Percona Release Lifecycle Overview&lt;/a> page.&lt;/p>
&lt;p>As part of our support policy, &lt;strong>Percona will continue to provide advisory support for databases running on EOL platforms&lt;/strong>, but effective &lt;strong>April 1, 2025&lt;/strong>, we have discontinued:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Delivery of new packages or binary builds&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Distribution of hotfixes or bug fixes for Percona software on Ubuntu 20.04&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OS-level support for issues not related to the database itself&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>However, all existing packages will remain available for download.&lt;/p>
&lt;p>We are committed to ensuring a smooth and seamless experience for our users. Migrating to a supported operating system will ensure that you continue to receive security updates, bug fixes, and new features for our products.&lt;/p>
&lt;p>We encourage you to take action promptly and plan your migration from Ubuntu Focal to a supported operating system. Each operating system vendor has different supported migration or upgrade paths to their next major release. Please &lt;a href="https://www.percona.com/services" target="_blank" rel="noopener noreferrer">contact us&lt;/a> if you need assistance migrating your database to a different supported OS platform – we will be happy to assist you!&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> If requested by a customer, we may provide updated Percona packages for up to six months beyond the Percona EOL date, as a courtesy grace period.&lt;/p></content:encoded><author>Julia Vural</author><category>Ubuntu</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/05/eol-ubuntu_hu_d33ab1f53ade240d.jpg"/><media:content url="https://percona.community/blog/2025/05/eol-ubuntu_hu_78dbe9450f5c4a1.jpg" medium="image"/></item><item><title>Percona Bug Report: April 2025</title><link>https://percona.community/blog/2025/05/19/percona-bug-report-april-2025/</link><guid>https://percona.community/blog/2025/05/19/percona-bug-report-april-2025/</guid><pubDate>Mon, 19 May 2025 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://jira.percona.com/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs.&lt;/p>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8846" target="_blank" rel="noopener noreferrer">PS-8846&lt;/a>: The ALTER INSTANCE RELOAD TLS thread gets stuck. This happens in instances with a high new connections rate (>60/s) but not in all instances. Percona and Upstream did not fix it properly because the current implementation of ALTER INSTANCE RELOAD TLS requires all existing SSL connections to be closed. “A thread that executes ALTER INSTANCE RELOAD TLS tries to acquire an RCU Lock(Read-Copy-Update), waiting for the number of readers to become 0. In other words, when the server has a constant flow of new incoming SSL connections, the chances of acquiring this lock are pretty low.” Therefore, Percona and Oracle only partially fixed this; this fix should improve this behaviour.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.32-24, 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9609" target="_blank" rel="noopener noreferrer">PS-9609&lt;/a>: The audit_log_filter can’t be installed when the server is using component_keyring_kmip&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.39-30&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42-33&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9628" target="_blank" rel="noopener noreferrer">PS-9628&lt;/a>: The binlog_encryption does not work with component_keyring_kmip&lt;/p>
&lt;p>&lt;strong>&lt;strong>Reported Affected Version/s&lt;/strong>&lt;/strong>: 8.0.40-31&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42-33&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9664" target="_blank" rel="noopener noreferrer">PS-9664&lt;/a>: With a very simple workload, MyRocks allocates a lot of memory and does not free it when the workload finishes. All instrumentation available either does not provide information about memory allocated or provides only part of it. As a result, users cannot predict how much RAM to install on the server that runs the MyRocks storage engine. With InnoDB same workload requires about 1.7G and frees about 0.5G once the job is finished.&lt;/p>
&lt;p>&lt;strong>&lt;strong>Reported Affected Version/s&lt;/strong>&lt;/strong>: 8.0.39-30&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9719" target="_blank" rel="noopener noreferrer">PS-9719&lt;/a>: When changing &lt;a href="https://dev.mysql.com/doc/mysql-replication-excerpt/5.7/en/replication-options-binary-log.html#sysvar_binlog_transaction_dependency_tracking" target="_blank" rel="noopener noreferrer">binlog_transaction_dependency_tracking&lt;/a> in high load workload, MySQL got a segmentation fault.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.40, 8.0.41-32, 8.0.33&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: &lt;a href="https://bugs.mysql.com/bug.php?id=117922" target="_blank" rel="noopener noreferrer">117922&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: “set global binlog_transaction_dependency_tracking = commit_order;”&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42-33, 8.4.5-5&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9768" target="_blank" rel="noopener noreferrer">PS-9768&lt;/a>: An unexpected duplicate error occurs when running a select query with a group by JSON data.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.41-32, 8.4.4-4&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: &lt;a href="https://bugs.mysql.com/bug.php?id=117927" target="_blank" rel="noopener noreferrer">117927&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Rebuilding the table with “alter table db_name.table_name = rocksdb;” can fix the issue.&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4512" target="_blank" rel="noopener noreferrer">PXC-4512&lt;/a>: When DDLs run against tables with foreign key references when there is a write load simultaneously. The issue is typically triggered during pt-online-schema-change execution, and after a dozen or so iterations, random PXC nodes will terminate with MDL BF-BF conflict. Sometimes, the writer fails, and sometimes, the other nodes, but it can be reproducible with just the RENAME query.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.33-25, 8.0.35-27, 8.0.36-28, 8.0.37-29, 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Only a full write stop would help.&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42, 8.4.5&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4648" target="_blank" rel="noopener noreferrer">PXC-4648&lt;/a>: After upgrading from 8.0.41 to 8.4.3, the node can’t join the group with the following error.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ERROR] [MY-000000] [Galera] /mnt/jenkins/workspace/pxc80-autobuild-RELEASE/test/rpmbuild/BUILD/Percona-XtraDB-Cluster-8.4.3/percona-xtradb-cluster-galera/gcs/src/gcs_group.cpp:group_check_proto_ver():343: Group requested gcs_proto_ver: 5, max supported by this node: 4.Upgrade the node before joining this group.Need to abort.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The problem is that 8.0.41 and 8.4.4 use Galera 26.4.21. It introduced the GCS protocol version 5, 8.0.40 and 8.4.3 using Galera 26.4.20. So, the node that doesn’t understand protocol 5 tries to connect to a cluster that uses protocol 5.&lt;/p>
&lt;p>It is fixed as a documented bug, which can be seen &lt;a href="https://docs.percona.com/percona-xtradb-cluster/8.4/upgrade-guide.html?h=upgrade+newest+8.0+version+ensure+is+newer+corresponding+8.4+plan" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.4.3&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.4.4&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4638" target="_blank" rel="noopener noreferrer">PXC-4638&lt;/a>: The binlog_utils_udf plugin fails to access binlog files correctly after an SST (State Snapshot Transfer) due to inconsistencies in the mysql-bin.index file.  After an SST, the first entry in the mysql-bin.index file can be incorrectly formatted with a relative path, while subsequent entries use absolute paths. This inconsistency can prevent the binlog_utils_udf plugin from locating the correct binlog files.&lt;/p>
&lt;p>The resulting mysql-bin.index content after SST might appear as:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql-bin.000010
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/home/user/sandboxes/pxc_msb_8_0_40/node2/data/mysql-bin.000011
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/home/user/sandboxes/pxc_msb_8_0_40/node2/data/mysql-bin.000012 &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.40, 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Rewriting binlog.index and adding the “./” prefix to the first entry, and running flush binary logs resolved this issue.&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42, 8.4.5&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-3576" target="_blank" rel="noopener noreferrer">PXC-3576&lt;/a>: Deploying a new installation using the setting lower_case_table_names=1 on the startup generates the following entry on the log:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[Warning] [MY-010324] [Server] 'db' entry 'percona_schema mysql.pxc.sst.role@localhost' had database in mixed case that has been forced to lowercase because lower_case_table_names is set. It will not be possible to remove this privilege using REVOKE.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Looking at the mysql.db, we can see the deployment was able to create the mysql.pxc.sst.role mapping an uppercase database name:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> select * from mysql.db where user='mysql.pxc.sst.role';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+--------------------+--------------------+------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Host | Db | User | Select_priv | Insert_priv | Update_priv | Delete_priv | Create_priv | Drop_priv | Grant_priv | References_priv | Index_priv | Alter_priv | Create_tmp_table_priv | Lock_tables_priv | Create_view_priv | Show_view_priv | Create_routine_priv | Alter_routine_priv | Execute_priv | Event_priv | Trigger_priv |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+--------------------+--------------------+-------------+-------------+-------------+-------------+-------------+-----------+------------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| localhost | PERCONA_SCHEMA | mysql.pxc.sst.role | N | N | N | N | Y | N | N | N | N | N | N | N | N | N | N | N | N | N | N |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| localhost | percona_schema | mysql.pxc.sst.role | N | N | N | N | Y | N | N | N | N | N | N | N | N | N | N | N | N | N | N |&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This produces a duplicate entry for the percona_schema database and a warning message on the mysql.log.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.21-12.1, 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42, 8.4.5&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4657" target="_blank" rel="noopener noreferrer">PXC-4657&lt;/a>: When executing DML and DDL on the same table, the DML will get a deadlock error. If the DML does not change the data but matches, it won’t be replicated, for example.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql > UPDATE test.t SET d = d LIMIT 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.01 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rows matched: 1 Changed: 0 Warnings: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">If the table contains a trigger, the DML does not get a deadlock error and will be replicated to other nodes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CREATE TRIGGER `t_on_update`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AFTER UPDATE ON `t`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FOR EACH ROW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BEGIN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> INSERT INTO t_history (`d`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> VALUES (NEW.`d`);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">END&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When other nodes apply the DML and DDL, the applier threads will get an MDL BF-BF conflict.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.40, 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Using a single applier thread will skip the bug, but it may introduce a performance issue, such as flow control.&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.42, 8.4.5&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4664" target="_blank" rel="noopener noreferrer">PXC-4664&lt;/a>: Converting thd->rli_slave to target type Slave_worker* causes a segmentation fault.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.41&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2392" target="_blank" rel="noopener noreferrer">PT-2392&lt;/a>: pt-online-schema-change resume functionality doesn’t work with ADD INDEX&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.6.0, 3.7.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.1&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2322" target="_blank" rel="noopener noreferrer">PT-2322&lt;/a>: The issue reports that pt-mysql-summary does not correctly detect and display the jemalloc memory management library, even when it is enabled. Despite jemalloc being loaded and visible in the process memory map (/proc/&lt;mysqld_pid>/maps), the output from pt-mysql-summary is missing this information in some cases, unlike version 3.2.1, which correctly identifies and reports it.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.5.6, 3.5.7&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.1&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2422" target="_blank" rel="noopener noreferrer">PT-2422&lt;/a>: When using the –history option with pt-online-schema-change (pt-osc), the query responsible for updating the history entry with the new_table_name is not appropriately constrained by a primary or unique key. As a result, this UPDATE operation can inadvertently modify all entries in the history table, rather than just the intended row.&lt;/p>
&lt;p>This behavior can lead to significant issues when running multiple schema change operations in parallel, as the history entries for different migrations may interfere with each other, causing data consistency problems.&lt;/p>
&lt;p>Additionally, if a migration is paused and later resumed, this lack of key constraint can result in only a subset of the data being correctly copied to the new table, potentially leading to partial data loss or corruption when the final table swap occurs.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.5.6, 3.5.7&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.1&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2442" target="_blank" rel="noopener noreferrer">PT-2442&lt;/a>: Multiple security vulnerabilities have been identified in the latest version of Percona Toolkit, including:&lt;/p>
&lt;p>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-56171" target="_blank" rel="noopener noreferrer">CVE-2024-56171&lt;/a>: Use-After-Free Vulnerability in libxml2&lt;/p>
&lt;p>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-12797" target="_blank" rel="noopener noreferrer">CVE-2024-12797&lt;/a>: OpenSSL Raw Public Key Authentication Vulnerability&lt;/p>
&lt;p>&lt;a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-37967" target="_blank" rel="noopener noreferrer">CVE-2022-37967&lt;/a>: Windows Kerberos Elevation of Privilege Vulnerability&lt;/p>
&lt;p>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-24928" target="_blank" rel="noopener noreferrer">CVE-2025-24928&lt;/a>: Stack-Based Buffer Overflow in libxml2&lt;/p>
&lt;p>It is recommended that the associated advisories be reviewed and the necessary patches or upgrades be applied to mitigate the risk.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.7.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.7.0-1&lt;/p>
&lt;hr>
&lt;h2 id="pmm-percona-monitoring-and-management">PMM (Percona Monitoring and Management)&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13694" target="_blank" rel="noopener noreferrer">PMM-13694&lt;/a>: When using a non-default pg_stat_statements.max value, the calculated QPS displayed in QAN may be wrong.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.38.0, 2.44.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.2.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13807" target="_blank" rel="noopener noreferrer">PMM-13807&lt;/a>: pmm-agent crashed at query.Fingerprint due to query including a column named “value”&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.44.0, 3.0.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.2.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13847" target="_blank" rel="noopener noreferrer">PMM-13847&lt;/a>: PMM 3.0 doesn’t support running on a different uid/gid in Kubernetes&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.0.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13984" target="_blank" rel="noopener noreferrer">PMM-13984&lt;/a>: Percona Monitoring and Management (PMM) version 3.1 with OVA image currently cannot be imported into VMware environments.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 3.1.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12784" target="_blank" rel="noopener noreferrer">PMM-12784&lt;/a>: This error invalid GetActionRequest.ActionId: value length must be at least 1 runes. occurs in the QAN (Query Analytics) dashboard, indicating that the ActionId field in the GetActionRequest is empty or improperly formatted. This typically results from a missing or incorrectly populated Action ID parameter, which is required for retrieving query data.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.33.0, 2.41.0, 2.44.0, 3.0.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 3.2.0&lt;/p>
&lt;hr>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3421" target="_blank" rel="noopener noreferrer">PXB-3421&lt;/a>: XtraBackup fails when the –databases parameter contains a very long list of databases or has a large amount of whitespace before the actual database names. When the –databases parameter is provided with 1859 whitespace characters before a table name (e.g., db01.t1), XtraBackup crashes with a signal error. If the number of whitespace characters is reduced to 1858 or fewer, the backup proceeds successfully without error.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.35-31&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3426" target="_blank" rel="noopener noreferrer">PXB-3426&lt;/a>: Using KMIP component causes double free of memory on error paths.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.35-31&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 8.0.35-33, 8.4.0-3&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3392" target="_blank" rel="noopener noreferrer">PXB-3392&lt;/a>: xtrabackup doesn’t pick up –innodb-log-group-home-dir config parameter&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 8.0.35-30, 8.0.35-31, 8.0.35-32&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-703" target="_blank" rel="noopener noreferrer">K8SPG-703&lt;/a>: When using ttlSecondsAfterFinished, there is a potential race condition where the backup jobs may be deleted before the Percona operator has had sufficient time to reconcile the perconapgbackups objects. This issue can occur even with relatively long timeouts like 1m, 5m, or 30m, not just extremely short intervals.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.5.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.7.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1263" target="_blank" rel="noopener noreferrer">K8SPSMDB-1263&lt;/a>: While creating a 1.3TB logical backup, the replica’s state changes to “errored” after approximately 16 hours with the message:&lt;/p>
&lt;p>“failed to find CERTIFICATE”&lt;/p>
&lt;p>despite the backup continuing to run and eventually completing. This raises concerns about the validity of the backup and the ability to restore from it without manually altering its status to “success.”&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.13.0, 1.14.0, 1.15.0, 1.19.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1292" target="_blank" rel="noopener noreferrer">K8SPSMDB-1292&lt;/a>: When spec.tls.mode is set to requireTLS, physical backup restores fail with a “server selection timeout” error. This occurs because the operator cannot establish a secure connection to the MongoDB server, resulting in closed socket errors and the inability to disable Point-in-Time Recovery (PiTR).&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.21.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1294" target="_blank" rel="noopener noreferrer">K8SPSMDB-1294&lt;/a>: When using MCS on GKE 1.30, an API version mismatch occurs, resulting in the error:&lt;/p>
&lt;p>“no matches for kind ‘ServiceImport’ in version ’net.gke.io/v1alpha1’”&lt;/p>
&lt;p>This indicates that the expected ServiceImport kind is not available in the specified API version, preventing proper service discovery.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.20.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1336" target="_blank" rel="noopener noreferrer">K8SPSMDB-1336&lt;/a>: Restoring a backup into a new Kubernetes cluster can lead to “Time monotonicity violation” errors on config servers and mongos, causing the pods to restart. This occurs when the restored chunk version timestamps are earlier than the expected timestamps in the new cluster, resulting in tripwire assertions and persistent crashes.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.19.1&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: To Be Determined&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1548" target="_blank" rel="noopener noreferrer">K8SPXC-1548&lt;/a>: Failed to delete old backups on Google Cloud Storage&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 1.14.0, 1.15.1&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 1.18.0&lt;/p>
&lt;hr>
&lt;h2 id="pbm-percona-backup-for-mongodb">PBM (Percona Backup for MongoDB)&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1482" target="_blank" rel="noopener noreferrer">PBM-1482&lt;/a>: Selective Restore with replset-remapping hangs on oplog replay and doesn’t finish&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.5.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.10.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1487" target="_blank" rel="noopener noreferrer">PBM-1487&lt;/a>: Error Location6493100 on mongos after successful logical restore or PITR&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.8.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.10.0&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PBM-1531" target="_blank" rel="noopener noreferrer">PBM-1531&lt;/a>: PBM Restore getting randomly stuck&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s&lt;/strong>: 2.6.0, 2.7.0, 2.8.0, 2.9.0&lt;/p>
&lt;p>&lt;strong>Upstream Bug&lt;/strong>: Not Applicable&lt;/p>
&lt;p>&lt;strong>Workaround/Fix&lt;/strong>: Not Available&lt;/p>
&lt;p>&lt;strong>Fixed/Planned Version/s&lt;/strong>: 2.10.0&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;p>&lt;a href="https://jira.percona.com" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p></content:encoded><author>Aaditya Dubey</author><category>PMM</category><category>Kubernetes</category><category>MySQL</category><category>MongoDB</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/05/BugReportApril2025_hu_9ef0f28e35b29c61.jpg"/><media:content url="https://percona.community/blog/2025/05/BugReportApril2025_hu_a174c2ff678d8a6c.jpg" medium="image"/></item><item><title>Progress report on pg_tde - GA extension is nearer every day!</title><link>https://percona.community/blog/2025/05/08/progress-report-on-pg_tde-ga-extension-is-nearer-every-day/</link><guid>https://percona.community/blog/2025/05/08/progress-report-on-pg_tde-ga-extension-is-nearer-every-day/</guid><pubDate>Thu, 08 May 2025 00:00:00 UTC</pubDate><description>Another week, another blogpost about the state of open source Transparent Data Encryption (TDE) for PostgreSQL.</description><content:encoded>&lt;p>Another week, another blogpost about the state of open source Transparent Data Encryption (TDE) for PostgreSQL.&lt;/p>
&lt;p>First off, thank you for all the feedback shared so far!&lt;/p>
&lt;p>Whether it’s reports about deployment issues with &lt;code>pg_tde&lt;/code>, integration with KMS, missing features or gaps in our documentation, we truly appreciate it! Your input helps us build a better, more complete solution and to properly prioritize what’s next.&lt;/p>
&lt;h2 id="whats-the-word">What’s the word?&lt;/h2>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2025/05/big-bird-sesame-street.gif" alt="Progress report on pg_tde - Bird" />&lt;/figure>&lt;/p>
&lt;p>We know many of you are eagerly waiting for a production-ready release of &lt;code>pg_tde&lt;/code>, and today we’ve got some good news! You may remember the last release was a Release Candidate (RC), now we’re gearing up to launch RC2.&lt;/p>
&lt;p>If you’re not familiar with the terminology, this simply means we’re one step closer to General Availability (GA).&lt;/p>
&lt;p>Our nightly builds are always there if you want to keep up with the latest updates, but this new milestone release highlights the fixes to some pain points that our tests and your feedback helped uncover.&lt;/p>
&lt;h2 id="when-to-expect-rc2">When to expect RC2?&lt;/h2>
&lt;p>This post is just a heads-up. The actual release is planned for early next week (after May 12, 2025). &lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">Keep an eye out&lt;/a>.&lt;/p>
&lt;p>In the meantime, feel free to check what’s coming in RC2. And if you’d like to benefit from these improvements right away, nightly builds are the way to go.&lt;/p>
&lt;h2 id="whats-new-in-rc2-compared-to-rc1">What’s new in RC2 compared to RC1?&lt;/h2>
&lt;p>Some recent documentation updates are already live, and we’d love to hear your thoughts on them. Don’t be a stranger, let us know how do you find them!&lt;/p>
&lt;p>As for the code, here are the key changes:&lt;/p>
&lt;ul>
&lt;li>KMS configuration improvements including:
&lt;ul>
&lt;li>New parameter for passing a client certificate when configuring a KMIP provider&lt;/li>
&lt;li>Compatibility updates for key management systems (KMS)
&lt;ul>
&lt;li>Thales CypherTrust&lt;/li>
&lt;li>Fortanix Data Security Manager&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Validation enforcement when adding key provider configurations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>WAL improvements, hardening encryption in our beta WAL support&lt;/li>
&lt;li>Security enhancements for multi-tenancy scenarios&lt;/li>
&lt;li>Other updates like:
&lt;ul>
&lt;li>Added &lt;code>pg_tde_verify_default_key()&lt;/code> and &lt;code>pg_tde_default_key_info()&lt;/code> functions&lt;/li>
&lt;li>Fixed support for logical replication&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="how-to-use-nightly-builds">How to use nightly builds&lt;/h2>
&lt;p>In case you’re wondering what nightly builds are: think of them as automatically generated versions of the software with the latest changes, usually built overnight 😎. They’re useful for testing (especially for integration testing) and for those, like our developers, who want to work with the freshest code.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1024" height="1024" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2025/05/nightly_builds_hu_e760d94caca316c5.webp 768w, https://percona.community/blog/2025/05/nightly_builds_hu_f4fff763500aff4e.webp 480w, https://percona.community/blog/2025/05/nightly_builds_hu_d4331f3b72d20a58.webp 1024w" src="https://percona.community/blog/2025/05/nightly_builds_hu_d4331f3b72d20a58.webp" alt="Progress report on pg_tde - nightly builds" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>No elephants have been hurt to create our nightly builds! We rely solely on CI/CD automation!&lt;/p>
&lt;p>You can find them in our &lt;a href="https://repo.percona.com/ppg-17.0/" target="_blank" rel="noopener noreferrer">experimental repo&lt;/a>. Do note, they’re currently only available for x86_64 and a limited set of operating systems:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://repo.percona.com/ppg-17.0/apt/pool/experimental/" target="_blank" rel="noopener noreferrer">Ubuntu Jammy/Noble&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://repo.percona.com/ppg-17.0/yum/experimental" target="_blank" rel="noopener noreferrer">OL/Rocky 8/9&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Jan Wieremjewicz</author><category>PG_TDE</category><category>Postgres</category><category>Opensource</category><category>pg_jan</category><media:thumbnail url="https://percona.community/blog/2025/05/jan-tde_hu_13de4022f636e6a6.jpg"/><media:content url="https://percona.community/blog/2025/05/jan-tde_hu_f00340f61063c5c3.jpg" medium="image"/></item><item><title>Setting Up and Monitoring MongoDB 8 Replica Sets with PMM 3 Using Docker: A Beginner-Friendly Guide</title><link>https://percona.community/blog/2025/03/18/setting-up-and-monitoring-mongodb-8-replica-sets-with-pmm-3-using-docker-a-beginner-friendly-guide/</link><guid>https://percona.community/blog/2025/03/18/setting-up-and-monitoring-mongodb-8-replica-sets-with-pmm-3-using-docker-a-beginner-friendly-guide/</guid><pubDate>Tue, 18 Mar 2025 00:00:00 UTC</pubDate><description>This guide explains how to set up a MongoDB 8 Replica Set and monitor it using PMM 3, all within Docker. We’ll guide you through the steps to create a local environment, configure the necessary components, and connect them for effective monitoring and management.</description><content:encoded>&lt;p>This guide explains how to set up a MongoDB 8 Replica Set and monitor it using PMM 3, all within Docker. We’ll guide you through the steps to create a local environment, configure the necessary components, and connect them for effective monitoring and management.&lt;/p>
&lt;blockquote>
&lt;p>The guide is written in detail for beginners. In &lt;a href="#conclusion">the conclusion&lt;/a> section there are ready configurations for the experienced.&lt;/p>&lt;/blockquote>
&lt;p>The recent &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/release-notes/3.0.0.html" target="_blank" rel="noopener noreferrer">release&lt;/a> of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/index.html" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management 3&lt;/a> introduces several new features:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Upgraded Grafana version for an improved user experience.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Rootless containers for enhanced security.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ARM support for the pmm-client.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Monitoring capabilities for MongoDB 8, along with &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/reference/dashboards/dashboard-mongodb-router-summary.html" target="_blank" rel="noopener noreferrer">new dashboards&lt;/a>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>This article is intended for developers and DBAs who want to experiment with these tools locally using Docker. We will cover the following steps to set everything up and test the functionality:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Launch the PMM 3 pmm-server for monitoring and open it in a browser.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Install MongoDB, starting with a standalone server, and then convert it into a Replica Set with three nodes. &lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> images are used in this article.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Configure the pmm-client for MongoDB to send metrics to the pmm-server.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Explore the PMM 3 &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/reference/dashboards/dashboard-mongodb-router-summary.html" target="_blank" rel="noopener noreferrer">dashboards&lt;/a>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="3294" height="1856" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_c8be583a58f680b4.webp 768w, https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_4cf647de2679fdc0.webp 480w, https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_2345a07332060eea.webp 1400w" src="https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_2345a07332060eea.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Dashboard - MongoDB Replica Set Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We will use Docker Compose to define and run multiple containers efficiently through a single &lt;code>docker-compose.yaml&lt;/code> file.&lt;/p>
&lt;p>If you’re ready to dive into the world of Dockerized database monitoring and management, let’s get started!&lt;/p>
&lt;h2 id="step-zero-preparation">Step Zero: Preparation&lt;/h2>
&lt;p>To get started, you need a terminal to run Docker commands and a text editor to modify the docker-compose.yaml file.&lt;/p>
&lt;p>You also need Docker installed on your system. If Docker is not installed, follow these instructions to set it up:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Docker Desktop: This application includes both Docker and Docker Compose. It is available for multiple operating systems. This guide uses Docker Desktop on macOS with an Apple Silicon ARM processor. &lt;a href="https://www.docker.com/products/docker-desktop/" target="_blank" rel="noopener noreferrer">Download Docker Desktop&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Docker and Docker Compose (separately): If preferred, install Docker and Docker Compose individually. Use the following links for guidance:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.docker.com/get-started/" target="_blank" rel="noopener noreferrer">Download Docker for your OS&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.docker.com/compose/install/" target="_blank" rel="noopener noreferrer">Download Docker Compose&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="command-to-verify-installation">Command to Verify Installation:&lt;/h3>
&lt;p>Verify Docker Compose Installation: If you’re using Docker Desktop, Docker Compose is included. If you installed Docker Compose separately, you can verify the installation with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker-compose --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This should return the version of Docker Compose installed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">➜ community git:&lt;span class="o">(&lt;/span>main&lt;span class="o">)&lt;/span> ✗ docker-compose --version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Docker Compose version v2.21.0-desktop.1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once Docker and Docker Compose are installed and verified, you are ready to move on to the next steps of deploying PMM 3 and MongoDB in Docker.&lt;/p>
&lt;h3 id="project-directory">Project Directory&lt;/h3>
&lt;p>Create a directory where we’ll store the configuration and necessary files. For example, I created a directory named pmm-mongodb-setup and navigated into it.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mkdir pmm-mongodb-setup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> pmm-mongodb-setup&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="step-one-starting-pmm-3-using-docker-compose">Step One: Starting PMM 3 Using Docker Compose&lt;/h2>
&lt;p>To start with, we will launch PMM 3, specifically the pmm-server, which we will later access via a browser.&lt;/p>
&lt;h3 id="create-the-docker-compose-configuration-file">Create the Docker Compose Configuration File&lt;/h3>
&lt;p>First, create a file named &lt;code>docker-compose.yaml&lt;/code> in your project directory. Then, copy and paste the following configuration into the file to set up PMM 3:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'3'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pmm-server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/pmm-server:3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">platform&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"linux/amd64"&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Specifies that Docker should use the image for the amd64 architecture, which is necessary if the container doesn't support ARM and your host system is ARM (e.g., Mac with Apple Silicon).&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-server&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">8080&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">443&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Defines a command to check the container's health and sets the timing for executions and retries.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"CMD-SHELL"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"curl -k -f -L https://pmm-server:8443 > /dev/null 2>&amp;1 || exit 1"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Explanation:&lt;/p>
&lt;p>We define the first &lt;code>pmm-server&lt;/code> service to use the &lt;code>percona/pmm-server:3&lt;/code> image&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>platform&lt;/code>: This parameter ensures compatibility with ARM-based processors, such as my Mac with Apple Silicon, by instructing Docker to use the image for the amd64 architecture.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>healthcheck&lt;/code>: This parameter performs a check to confirm that the container has started successfully.&lt;/p>
&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;h3 id="start-the-pmm-3-container">Start the PMM 3 Container&lt;/h3>
&lt;p>Save the &lt;code>docker-compose.yaml&lt;/code> file, and then use the following command to start the PMM 3 container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command will download the PMM 3 image if it is not already available locally and start the container in detached mode.&lt;/p>
&lt;p>Expected result in the terminal:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup docker-compose up -d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>+&lt;span class="o">]&lt;/span> Running 2/2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Network pmm-mongodb-setup_default Created 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-server Started 0.9s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected result in Docker Desktop:
&lt;figure>&lt;img width="2494" height="1146" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/docker-pmm-start_hu_c86b9cd3963902ce.webp 768w, https://percona.community/blog/2025/03/docker-pmm-start_hu_4fd3ed471122fafc.webp 480w, https://percona.community/blog/2025/03/docker-pmm-start_hu_ac6ba1553de2b093.webp 1400w" src="https://percona.community/blog/2025/03/docker-pmm-start_hu_ac6ba1553de2b093.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop Start" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="open-pmm-in-a-browser">Open PMM in a browser&lt;/h3>
&lt;p>Now you can open PMM in your browser at http://localhost. Use admin/admin as the username and password to log in. When prompted to change the password, skip this step by clicking the Skip button. Since this is a test setup, we need a simple password for other containers.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1192" height="1386" sizes="(max-width: 1192px) 100vw, 1192px" srcset="https://percona.community/blog/2025/03/pmm-login_hu_c33dac9dbf003690.webp 768w, https://percona.community/blog/2025/03/pmm-login_hu_bb0a4014f0707f08.webp 480w, https://percona.community/blog/2025/03/pmm-login_hu_548c33f0dee38e54.webp 1192w" src="https://percona.community/blog/2025/03/pmm-login_hu_548c33f0dee38e54.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Login" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3300" height="1784" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/pmm-home_hu_80107ef0aea27cb4.webp 768w, https://percona.community/blog/2025/03/pmm-home_hu_c0c76f5afd2f4e1.webp 480w, https://percona.community/blog/2025/03/pmm-home_hu_f5a6ea1e7f2e9ee7.webp 1400w" src="https://percona.community/blog/2025/03/pmm-home_hu_f5a6ea1e7f2e9ee7.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Home Page" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now, PMM 3 is successfully running using Docker Compose.&lt;/p>
&lt;h2 id="step-two-starting-mongodb">Step Two: Starting MongoDB&lt;/h2>
&lt;p>We start by launching a standalone MongoDB service. This simple configuration allows us to quickly understand how it operates before moving on to a more advanced setup with a Replica Set.&lt;/p>
&lt;p>To keep the database data persistent, even after a restart, a &lt;code>volume&lt;/code> is used for MongoDB data storage. Add the following configuration to the &lt;code>docker-compose.yaml&lt;/code> file under the &lt;code>pmm-server&lt;/code> service:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"percona/percona-server-mongodb:8.0-multi"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-data:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">databaseAdmin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"27017:27017"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"mongod"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--port"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"27017"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--bind_ip_all"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--profile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"2"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--slowms"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"200"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--rateLimit"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"100"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"CMD-SHELL"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"mongosh --eval 'db.adminCommand(\"ping\")' --quiet"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># MongoDB data storage volume&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Explanation:&lt;/p>
&lt;ul>
&lt;li>&lt;code>environment&lt;/code>: Configures the root user’s credentials (username and password).&lt;/li>
&lt;li>&lt;code>command&lt;/code>: Sets additional parameters for MongoDB:
&lt;ul>
&lt;li>&lt;code>bind_ip_all&lt;/code>: Allows external access to the database, for instance, through MongoDB Compass.&lt;/li>
&lt;li>&lt;code>profile&lt;/code>: Enables profiling settings to support Query Analytics (QAN) in PMM.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>healthcheck&lt;/code>: Ensures the container starts successfully by executing a health check command.&lt;/li>
&lt;li>&lt;code>volumes:&lt;/code>: Creates the specified volume for MongoDB data storage. Defines a Docker volume to store MongoDB data persistently.&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;h3 id="launching-the-configuration">Launching the Configuration&lt;/h3>
&lt;p>Save the updated &lt;code>docker-compose.yaml&lt;/code> file and launch the MongoDB service by running the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Docker Compose checks the configuration and starts the MongoDB container.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup docker-compose up -d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>+&lt;span class="o">]&lt;/span> Running 3/3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Volume &lt;span class="s2">"pmm-mongodb-setup_mongodb-data"&lt;/span> Created 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-mongodb-setup-mongodb-1 Started 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-server Running 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verifying MongoDB in Docker Desktop:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2496" height="908" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_9a67b70fad89a05f.webp 768w, https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_1bb5ddbda6783982.webp 480w, https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_57ab2c40895fbf9a.webp 1400w" src="https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_57ab2c40895fbf9a.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="step-three-pmm-client">Step Three: PMM Client&lt;/h2>
&lt;p>At this point, we have both the PMM Server, which is accessible in the browser, and MongoDB running. To transfer metrics from MongoDB to the PMM Server, a container with &lt;code>pmm-client&lt;/code> needs to be started.&lt;/p>
&lt;p>Add another service &lt;code>pmm-client&lt;/code> to the &lt;code>docker-compose.yaml&lt;/code> file, right after &lt;code>mongodb&lt;/code>. Note that `volumes:`` must remain at the bottom of the file.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pmm-client&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/pmm-client:3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-client&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pmm-server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_ADDRESS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-server:8443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_INSECURE_TLS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_CONFIG_FILE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">config/pmm-agent.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP_FORCE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_PRERUN_SCRIPT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">>&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin status --wait=10s &amp;&amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin add mongodb --username=databaseAdmin --password=password --host=mongodb --port=27017 --query-source=profiler&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Explanation:&lt;/p>
&lt;ul>
&lt;li>&lt;code>depends_on&lt;/code>: Ensures that pmm-client starts only after pmm-server and mongodb have started successfully and passed the healthcheck.&lt;/li>
&lt;li>&lt;code>PMM_AGENT_PRERUN_SCRIPT&lt;/code>: The pmm-admin add mongodb command adds the MongoDB service to PMM for monitoring.&lt;/li>
&lt;li>&lt;code>PMM_AGENT_SERVER_ADDRESS&lt;/code>: Specifies the PMM Server address and uses port 8443.&lt;/li>
&lt;li>&lt;code>PMM_AGENT_SERVER_USERNAME&lt;/code> and &lt;code>PMM_AGENT_SERVER_PASSWORD&lt;/code>: Update these values if you have changed the PMM login credentials.&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;h3 id="applying-the-updated-configuration">Applying the Updated Configuration&lt;/h3>
&lt;p>Run the following command to apply the updated docker-compose.yaml configuration and start the pmm-client service:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After running the command, you should see the pmm-client container start successfully:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup docker-compose up -d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>+&lt;span class="o">]&lt;/span> Running 3/3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-mongodb-setup-mongodb-1 Healthy 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-server Healthy 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-client Started 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Open PMM in your browser. On the homepage, you should now see MongoDB listed as a monitored service:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2272" height="1116" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/pmm-home-mongodb_hu_eb74842da2768d12.webp 768w, https://percona.community/blog/2025/03/pmm-home-mongodb_hu_56dc6ad4fbe62b33.webp 480w, https://percona.community/blog/2025/03/pmm-home-mongodb_hu_abf1cacccfc8a5ff.webp 1400w" src="https://percona.community/blog/2025/03/pmm-home-mongodb_hu_abf1cacccfc8a5ff.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="step-four-convert-a-standalone-mongodb-to-a-replica-set">Step Four: Convert a Standalone MongoDB to a Replica Set&lt;/h2>
&lt;p>A single MongoDB instance works well for development and testing. At this point, you can already connect to the database from your application or tools such as MongoDB Compass and run various NoSQL queries.&lt;/p>
&lt;p>However, the goal is to deploy a Replica Set consisting of three replicas, which is recommended for production and operational setups. For now, we set up the Replica Set on a single machine with a single docker-compose, which is intended for testing and development purposes only.&lt;/p>
&lt;p>Both the mongodb and pmm-client services need to be updated.&lt;/p>
&lt;h3 id="stopping-all-services">Stopping All Services&lt;/h3>
&lt;p>First, stop all the currently running services:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker-compose down &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Result:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup docker-compose down
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>+&lt;span class="o">]&lt;/span> Running 4/4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-client Removed 0.3s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-mongodb-setup-mongodb-1 Removed 0.4s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-server Removed 4.5s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Network pmm-mongodb-setup_default Removed&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="generating-a-key-file">Generating a Key File&lt;/h3>
&lt;p>To run three MongoDB replicas that can securely communicate with each other, a key file is required.&lt;/p>
&lt;p>Create a &lt;code>secrets&lt;/code> folder next to the &lt;code>docker-compose.yaml&lt;/code> file and generate the &lt;code>mongodb-keyfile&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mkdir secrets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl rand -base64 &lt;span class="m">128&lt;/span> > secrets/mongodb-keyfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod &lt;span class="m">600&lt;/span> secrets/mongodb-keyfile&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this case, the mongodb-keyfile will be generated inside the secrets folder. If your operating system does not support these commands, manually create the secrets folder and add a mongodb-keyfile with the following content:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">rVLhIK2PhZKGxysjwMR4t1OmNppqdAzEs408hrbzg95D146mn9YENixId6pvIGCA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Cy9hc1k6OKKabbv7Rm347NwSFxbdPPx0/jnaO80U/a6/mv0XqSmEl8wdR91b4jIm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">d98LobplwRs4b7g9cnLMUAIULr0WG+J36NtKIA6q4eE=&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="add-three-mongodb-services-for-replica-set">Add three mongodb services for Replica Set&lt;/h3>
&lt;p>Remove the existing mongodb service from the docker-compose.yaml file and replace it with three Replica Set services:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs101&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-server-mongodb:8.0-multi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mongodb-rs101&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"27017:27017"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"mongod"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--port"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"27017"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--replSet"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"rs"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--keyFile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"/etc/secrets/mongodb-keyfile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--bind_ip_all"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--profile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"2"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--slowms"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"200"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--rateLimit"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"100"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">databaseAdmin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-data-101:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./secrets:/etc/secrets:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"CMD-SHELL"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"mongosh --host localhost --port 27017 --username databaseAdmin --password password --authenticationDatabase admin --eval 'rs.status().ok || 1'"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs102&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-server-mongodb:8.0-multi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mongodb-rs102&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"28017:28017"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"mongod"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--port"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"28017"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--replSet"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"rs"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--keyFile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"/etc/secrets/mongodb-keyfile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--bind_ip_all"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--profile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"2"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--slowms"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"200"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--rateLimit"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"100"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">databaseAdmin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-data-102:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./secrets:/etc/secrets:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"CMD-SHELL"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"mongosh --host localhost --port 28017 --username databaseAdmin --password password --authenticationDatabase admin --eval 'rs.status().ok || 1'"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs103&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-server-mongodb:8.0-multi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mongodb-rs103&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"29017:29017"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"mongod"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--port"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"29017"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--replSet"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"rs"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--keyFile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"/etc/secrets/mongodb-keyfile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--bind_ip_all"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--profile"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"2"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--slowms"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"200"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"--rateLimit"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"100"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">databaseAdmin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MONGO_INITDB_ROOT_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-data-103:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./secrets:/etc/secrets:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"CMD-SHELL"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"mongosh --host localhost --port 29017 --username databaseAdmin --password password --authenticationDatabase admin --eval 'rs.status().ok || 1'"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Key Points:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>./secrets:/etc/secrets:ro:&lt;/code> Mounts the key file from your disk into the container.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ports:&lt;/code>: Each replica runs on a separate port since they are hosted on the same machine.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>command:&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>--keyFile&lt;/code>: Enables replication and uses the key file for secure communication between replicas.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>--replSet&lt;/code>: Defines a Replica set parameter named rs.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;code>healthcheck:&lt;/code>: Ensures that pmm-client starts only after the Replica Set is initialized.&lt;/p>
&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;h3 id="adding-volumes">Adding Volumes&lt;/h3>
&lt;p>Define three separate volumes for data storage:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-data-101&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-data-102&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">mongodb-data-103:&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="initializing-the-replica-set">Initializing the Replica Set&lt;/h3>
&lt;p>Add another service to initialize the Replica Set (after &lt;code>mongodb-rs103&lt;/code>):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs-init&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-server-mongodb:8.0-multi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">rs-init&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-rs101&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-rs102&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb-rs103&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">entrypoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s2">"sh"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"-c"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s2">"until mongosh --host mongodb-rs101 --port 27017 --username databaseAdmin --password password --authenticationDatabase admin --eval 'print(\"waited for connection\")'; do sleep 5; done &amp;&amp; \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> mongosh --host mongodb-rs101 --port 27017 --username databaseAdmin --password password --authenticationDatabase admin --eval 'config={\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"mongodb-rs101:27017\"},{\"_id\":1,\"host\":\"mongodb-rs102:28017\"},{\"_id\":2,\"host\":\"mongodb-rs103:29017\"}],\"settings\":{\"keyFile\":\"/etc/secrets/mongodb-keyfile\"}};rs.initiate(config);'"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./secrets:/etc/secrets:ro&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This service connects to one of the replicas and initializes the Replica Set configuration.&lt;/p>
&lt;h3 id="updating-pmm-client">Updating pmm-client&lt;/h3>
&lt;p>Finally, modify the pmm-client service to register all three Replica Set nodes:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pmm-client&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/pmm-client:3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-client&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pmm-server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs101&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs102&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb-rs103&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_ADDRESS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-server:8443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_INSECURE_TLS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_CONFIG_FILE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">config/pmm-agent.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP_FORCE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_PRERUN_SCRIPT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">>&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin status --wait=10s &amp;&amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin add mongodb --service-name=mongodb-rs101 --username=databaseAdmin --password=password --host=mongodb-rs101 --port=27017 --query-source=profiler &amp;&amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin add mongodb --service-name=mongodb-rs102 --username=databaseAdmin --password=password --host=mongodb-rs102 --port=28017 --query-source=profiler &amp;&amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pmm-admin add mongodb --service-name=mongodb-rs103 --username=databaseAdmin --password=password --host=mongodb-rs103 --port=29017 --query-source=profiler&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>Explanation:&lt;/p>
&lt;p>&lt;code>depends_on&lt;/code>: Ensures pmm-client waits until all replicas and pmm-server are initialized.&lt;/p>
&lt;p>&lt;code>PMM_AGENT_PRERUN_SCRIPT&lt;/code>: Adds all three replicas to PMM monitoring with the pmm-admin add mongodb command.&lt;/p>&lt;/blockquote>
&lt;h3 id="launching-the-configuration-1">Launching the Configuration&lt;/h3>
&lt;p>Start all services with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker-compose up -d &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected Output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup docker-compose up -d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>+&lt;span class="o">]&lt;/span> Running 10/10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Network pmm-mongodb-setup_default Created 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Volume &lt;span class="s2">"pmm-mongodb-setup_mongodb-data-101"&lt;/span> Created 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Volume &lt;span class="s2">"pmm-mongodb-setup_mongodb-data-102"&lt;/span> Created 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Volume &lt;span class="s2">"pmm-mongodb-setup_mongodb-data-103"&lt;/span> Created 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container mongodb-rs103 Healthy 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-server Healthy 1.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container mongodb-rs101 Healthy 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container mongodb-rs102 Healthy 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container rs-init Started 0.1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✔ Container pmm-client Started 0.0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ pmm-mongodb-setup&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Expected Output in Docker Desktop:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2310" height="964" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/docker-desktop-rs_hu_838eb38d67c57a4d.webp 768w, https://percona.community/blog/2025/03/docker-desktop-rs_hu_b46079c88410044d.webp 480w, https://percona.community/blog/2025/03/docker-desktop-rs_hu_3c89439eaa0eca50.webp 1400w" src="https://percona.community/blog/2025/03/docker-desktop-rs_hu_3c89439eaa0eca50.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="verifying-in-pmm">Verifying in PMM&lt;/h3>
&lt;p>Open PMM and explore dashboards such as the MongoDB Replica Set Summary, which displays information about your replicas and various metrics:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2714" height="1704" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/pmm-rs-services_hu_fac662d4ebfe11e3.webp 768w, https://percona.community/blog/2025/03/pmm-rs-services_hu_f4623126e59201ea.webp 480w, https://percona.community/blog/2025/03/pmm-rs-services_hu_608099da0c2ff1bb.webp 1400w" src="https://percona.community/blog/2025/03/pmm-rs-services_hu_608099da0c2ff1bb.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For example, I experimented by restarting one of the MongoDB services in Docker Desktop. The Replica Set switched the Primary replica, and when I simulated a failure by stopping a service, the monitoring dashboard reflected this event:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2004" height="702" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/pmm-rs-statuses_hu_31a807941e3123a1.webp 768w, https://percona.community/blog/2025/03/pmm-rs-statuses_hu_d97fd9b5f65672be.webp 480w, https://percona.community/blog/2025/03/pmm-rs-statuses_hu_2f2fcaed037ea02f.webp 1400w" src="https://percona.community/blog/2025/03/pmm-rs-statuses_hu_2f2fcaed037ea02f.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="connecting-to-mongodb-and-useful-commands">Connecting to MongoDB and Useful Commands&lt;/h2>
&lt;h3 id="connecting-to-mongodb">Connecting to MongoDB&lt;/h3>
&lt;p>There are several ways to connect to MongoDB depending on your setup and tools:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Using Docker Desktop&lt;/p>
&lt;p>If you are using Docker Desktop, you can select a container and open the Exec tab. This opens a terminal within the container.&lt;/p>
&lt;p>From there, you can connect to MongoDB using the mongosh shell with the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mongosh --host localhost --port &lt;span class="m">27017&lt;/span> -u databaseAdmin -p password --authenticationDatabase admin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2482" height="1634" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/03/mongodb-connect_hu_18ab67fe2ee91749.webp 768w, https://percona.community/blog/2025/03/mongodb-connect_hu_284a74949611f7c.webp 480w, https://percona.community/blog/2025/03/mongodb-connect_hu_1a072f9eccf5e6f8.webp 1400w" src="https://percona.community/blog/2025/03/mongodb-connect_hu_1a072f9eccf5e6f8.webp" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB Connect" loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Using Docker CLI&lt;/p>
&lt;p>If you’re running Docker without Docker Desktop, you can connect to the container using the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it &lt;container_name> bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>when you connect to the container, connect to MongoDB&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mongosh --host localhost --port &lt;span class="m">27017&lt;/span> -u databaseAdmin -p password --authenticationDatabase admin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Connecting from Applications or Tools&lt;/p>
&lt;p>If you are connecting through an application or a tool like MongoDB Compass, you can use connection strings tailored to your setup:&lt;/p>
&lt;p>Here are the MongoDB connection strings tailored for your use cases:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Standalone MongoDB: Connects directly to a single MongoDB instance.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mongodb://databaseAdmin:password@localhost:27017&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Primary Node: Forces a direct connection to the primary node in the Replica Set using the directConnection=true option.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mongodb://databaseAdmin:password@localhost:27017/?directConnection&lt;span class="o">=&lt;/span>true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Full Replica Set: Lists all Replica Set members and enables automatic failover using replicaSet=rs&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mongodb://databaseAdmin:password@localhost:27017,localhost:28017,localhost:29017/?replicaSet&lt;span class="o">=&lt;/span>rs&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;h3 id="useful-commands">Useful Commands&lt;/h3>
&lt;p>Here are some helpful commands for managing and troubleshooting your MongoDB setup:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Check the status of the Replica Set. After connecting to MongoDB, use the following command to retrieve the status of the Replica Set:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rs.status&lt;span class="o">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Check MongoDB runtime configuration. Use this command to view the configuration options for the running MongoDB instance:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">db.adminCommand&lt;span class="o">({&lt;/span> getCmdLineOpts: &lt;span class="m">1&lt;/span> &lt;span class="o">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this article, we explored deploying MongoDB using Docker and Docker Compose. We covered both a Single Instance MongoDB for simple setups and a MongoDB Replica Set for high availability, while integrating them with Percona Monitoring and Management (PMM) for monitoring.&lt;/p>
&lt;p>Here are the final configurations you can download:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Single Instance MongoDB + PMM 3: &lt;a href="https://gist.github.com/dbazhenov/fc954c9bd7f21e2ad17dffb4acfd7142" target="_blank" rel="noopener noreferrer">Link&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Replica Set MongoDB + PMM 3: &lt;a href="https://gist.github.com/dbazhenov/fd47167734230d294a4aa10da623d1f2" target="_blank" rel="noopener noreferrer">Link&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Thank you for reading! I look forward to your comments and questions.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>MongoDB</category><category>Docker</category><category>Opensource</category><category>PMM</category><media:thumbnail url="https://percona.community/blog/2025/03/pmm-mongodb-cover_hu_4a7029d1f994dce1.jpg"/><media:content url="https://percona.community/blog/2025/03/pmm-mongodb-cover_hu_3b760685321d77d.jpg" medium="image"/></item><item><title>Join Us Online: Stream About Percona Toolkit for MySQL!</title><link>https://percona.community/blog/2025/03/14/join-us-online-stream-about-percona-toolkit-for-mysql/</link><guid>https://percona.community/blog/2025/03/14/join-us-online-stream-about-percona-toolkit-for-mysql/</guid><pubDate>Fri, 14 Mar 2025 00:00:00 UTC</pubDate><description>Are you passionate about databases and want to stay on top of the latest advancements in MySQL and Percona Toolkit? You’re in luck! We are excited to invite you to our upcoming online stream, where we’ll delve into some exciting changes and updates.</description><content:encoded>&lt;p>Are you passionate about databases and want to stay on top of the latest advancements in MySQL and Percona Toolkit? You’re in luck! We are excited to invite you to our upcoming online stream, where we’ll delve into some exciting changes and updates.&lt;/p>
&lt;p>&lt;strong>Date:&lt;/strong> March 27, 2025&lt;br>
&lt;strong>Time:&lt;/strong> 13:30 GMT / 9:30 ET&lt;br>
&lt;strong>Streaming Live on:&lt;/strong> &lt;a href="https://www.linkedin.com/events/removingoffensivelanguagefrompe7307408691371077632/theater/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a> and &lt;a href="https://www.youtube.com/live/JOEpIQL7cXM" target="_blank" rel="noopener noreferrer">YouTube&lt;/a>&lt;/p>
&lt;h4 id="about-the-event">About the Event&lt;/h4>
&lt;p>Our featured speaker, Sveta Smirnova, Principal Support Engineering Coordinator at Percona, will share her insights on the recent updates in MySQL, focusing on the removal of offensive replication statements like START/STOP SLAVE. As the maintainer of the Percona Toolkit, Sveta had to rewrite numerous tools and libraries to accommodate these changes, resulting in significant updates to 511 files.&lt;/p>
&lt;p>This event is perfect for developers, DBAs, and anyone interested in databases. Sveta, an expert in MySQL and Percona Toolkit, is also an accomplished author and a speaker at international conferences on development and databases. During the stream, she will discuss the challenges she faced while renewing legacy code, including supporting SSL and handling the deprecation of certain tools.&lt;/p>
&lt;p>We invite you to join the discussion and share your experiences, questions, and insights. Engage directly with Sveta and other participants to gain a deeper understanding of the Percona Toolkit and its latest enhancements.&lt;/p>
&lt;h4 id="discussion-topics">Discussion Topics&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>Introduction to Percona Toolkit&lt;/strong>:
&lt;ul>
&lt;li>History and evolution of Percona Toolkit.&lt;/li>
&lt;li>Overview of the most popular tools and their uses.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Changes in MySQL&lt;/strong>:
&lt;ul>
&lt;li>Overview of new and legacy syntax.&lt;/li>
&lt;li>Implications of dropped offensive replication statements.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Adapting Percona Toolkit&lt;/strong>:
&lt;ul>
&lt;li>Challenges in renewing legacy code.&lt;/li>
&lt;li>Solutions implemented, including SSL support and deprecation of certain tools.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Practical Insights&lt;/strong>:
&lt;ul>
&lt;li>Detailed explanation of the changes made to 511 files.&lt;/li>
&lt;li>Fine-tuning and migration strategies for Percona Toolkit users.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="why-attend">Why Attend?&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>Insightful Content:&lt;/strong> Gain valuable knowledge on the latest changes in MySQL and how they impact Percona Toolkit.&lt;/li>
&lt;li>&lt;strong>Expert Guidance:&lt;/strong> Learn directly from Sveta Smirnova, an industry expert with extensive experience in database management.&lt;/li>
&lt;li>&lt;strong>Interactive Session:&lt;/strong> Have the opportunity to ask questions live and engage directly with the speaker.&lt;/li>
&lt;/ul>
&lt;h4 id="how-to-join">How to Join&lt;/h4>
&lt;p>Tune in on March 27, 2025, at 13:30 GMT / 9:30 ET, and watch the live stream on &lt;a href="https://www.linkedin.com/events/removingoffensivelanguagefrompe7307408691371077632/theater/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a> and &lt;a href="https://www.youtube.com/live/JOEpIQL7cXM" target="_blank" rel="noopener noreferrer">YouTube&lt;/a>. Don’t miss this chance to enhance your understanding of MySQL and Percona Toolkit while gaining practical insights from one of the best in the field.&lt;/p>
&lt;p>Mark your calendars, spread the word, and get ready for an informative session! We look forward to seeing you there!&lt;/p>
&lt;p>If you have any questions or need further information, feel free to reach out to us.&lt;/p>
&lt;p>📅 Add to Calendar&lt;br>
🔗 &lt;a href="https://www.linkedin.com/events/removingoffensivelanguagefrompe7307408691371077632/theater/" target="_blank" rel="noopener noreferrer">Join the LinkedIn Stream&lt;/a>&lt;br>
🔗 &lt;a href="https://www.youtube.com/live/JOEpIQL7cXM" target="_blank" rel="noopener noreferrer">Join the YouTube Stream&lt;/a>&lt;/p>
&lt;p>We can’t wait to see you at the event!&lt;/p></content:encoded><author>Sveta Smirnova</author><category>MySQL</category><category>Toolkit</category><category>Events</category><media:thumbnail url="https://percona.community/events/streams/Live-Sveta-Edith-march-27_hu_8acd1ec53a021f.jpg"/><media:content url="https://percona.community/events/streams/Live-Sveta-Edith-march-27_hu_b4d5e82775302e73.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 3 and rootless containers</title><link>https://percona.community/blog/2025/02/19/percona-monitoring-and-management-3-and-rootless-containers/</link><guid>https://percona.community/blog/2025/02/19/percona-monitoring-and-management-3-and-rootless-containers/</guid><pubDate>Wed, 19 Feb 2025 00:00:00 UTC</pubDate><description>In today’s landscape, where security breaches are a constant concern, reducing potential attack vectors is a top priority for any organization. Percona Monitoring and Management (PMM) has established itself as a reliable solution for database performance monitoring. With the release of PMM version 3, Percona has significantly strengthened its security posture, notably by introducing support for rootless container deployments. This advancement directly addresses a crucial security challenge and enhances the overall robustness and reliability of PMM.</description><content:encoded>&lt;p>In today’s landscape, where security breaches are a constant concern, reducing potential attack vectors is a top priority for any organization. Percona Monitoring and Management (PMM) has established itself as a reliable solution for database performance monitoring. With &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/release-notes/3.0.0.html" target="_blank" rel="noopener noreferrer">the release of PMM version 3&lt;/a>, Percona has significantly strengthened its security posture, notably by introducing support for rootless container deployments. This advancement directly addresses a crucial security challenge and enhances the overall robustness and reliability of PMM.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2600" height="1401" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/02/pmm3-homepage_hu_33adf12d2d514a2e.webp 768w, https://percona.community/blog/2025/02/pmm3-homepage_hu_d8cbb6537e970480.webp 480w, https://percona.community/blog/2025/02/pmm3-homepage_hu_38e360a9f9708b95.webp 1400w" src="https://percona.community/blog/2025/02/pmm3-homepage_hu_38e360a9f9708b95.webp" alt="Percona Monitoring and Management (PMM) 3.0.0" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The inherent risks associated with root privileges are well-documented. While many applications, including those containerized, have historically relied on root access, this practice presents a substantial security vulnerability. In the event of a successful exploit, an attacker gains comprehensive control over the host system. This risk is further exacerbated in environments with outdated software or complex configurations. Essentially, while the root user offers extensive capabilities, it also represents a significant potential liability that should be carefully mitigated.&lt;/p>
&lt;p>In this blog post we will look at the exact differences between PMM versions 2 and 3 and how they behave.&lt;/p>
&lt;h2 id="enforcing-pod-security-standards">Enforcing Pod Security Standards&lt;/h2>
&lt;p>&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" target="_blank" rel="noopener noreferrer">The Pod Security Standards&lt;/a> define three different policies to broadly cover the security spectrum. These policies are cumulative and range from highly-permissive to highly-restrictive.&lt;/p>
&lt;p>To enforce restrictions on a specific namespace we will create the following YAML manifest:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apiVersion: v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Namespace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: secure-namespace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> labels:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pod-security.kubernetes.io/enforce: restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pod-security.kubernetes.io/warn: restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pod-security.kubernetes.io/audit: restricted&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This YAML creates a namespace named secure-namespace. The pod-security.kubernetes.io/enforce: restricted label instructs Kubernetes to deny any pods in this namespace that violate the “restricted” PSS profile. The warn and audit labels are also very useful for monitoring and testing before fully enforcing the restricted policy.&lt;/p>
&lt;h2 id="deploy-pmm">Deploy PMM&lt;/h2>
&lt;p>We will execute a series of deployments to demonstrate the difference between PMM2 and PMM3 behavior in insecure and secure environments. All files can be found in this github repository: &lt;a href="https://github.com/spron-in/blog-data/tree/master/pmm3-rootless" target="_blank" rel="noopener noreferrer">spron-in/blog-data/pmm3-rootless&lt;/a>&lt;/p>
&lt;h3 id="regular-namespace">Regular namespace&lt;/h3>
&lt;p>&lt;strong>PMM2&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl apply -f 01.pmm2.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">% kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-server-0 1/1 Running 0 45s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I can connect to my PMM2 server with a Service that I created.&lt;/p>
&lt;p>&lt;strong>PMM3&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl apply -f 02.pmm3.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">% kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-server-0 1/1 Running 0 20s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I can connect to my PMM3 server with a Service that I created.&lt;/p>
&lt;h3 id="secure-namespace">Secure namespace&lt;/h3>
&lt;p>Let’s try to deploy both versions of Percona Monitoring and Management servers in a secure namespace. For both we will see the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl apply -f 01.pmm2.yaml -n secure-namespace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "pmm-server" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "pmm-server" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "pmm-server" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "pmm-server" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No resources found in secure-namespace namespace.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>There are no Pods created and if you describe the StatefulSet, you are going to see a similar error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Warning FailedCreate 5s (x15 over 87s) statefulset-controller create Pod pmm-server-0 in StatefulSet pmm-server failed error: pods "pmm-server-0" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "pmm-server" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "pmm-server" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "pmm-server" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "pmm-server" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The error will be the same for PMM2 and PMM3 manifests.&lt;/p>
&lt;h3 id="secure-namespace-and-security-contexts">Secure namespace and security contexts&lt;/h3>
&lt;p>We are now going to apply Pod and Container Security Contexts to both manifests.&lt;/p>
&lt;p>Under spec.containers we add everything that Kubernetes suggested us:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> - name: pmm-server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> securityContext:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> allowPrivilegeEscalation: false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> capabilities:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> drop:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ALL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runAsNonRoot: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> seccompProfile:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: 'RuntimeDefault'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>PMM2&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace apply -f 03.pmm2-secure.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-server-0 0/1 Error 2 (15s ago) 28s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Even though the PMM2 server Pod can be created now, it is failing to start. If you check the logs, you are going to see the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace logs pmm-server-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Error: Can't drop privilege as nonroot user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">For help, use /usr/local/bin/supervisord -h&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>PMM3&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace apply -f 04.pmm3-secure.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-server created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">% kubectl -n secure-namespace get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-server-0 1/1 Running 0 32s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>PMM3 starts just fine.&lt;/p>
&lt;h3 id="helm">Helm&lt;/h3>
&lt;p>The recommended approach to deploy PMM3 in Kubernetes is via Helm. You can find our helm charts in &lt;a href="https://github.com/percona/percona-helm-charts/tree/main/charts/pmm" target="_blank" rel="noopener noreferrer">percona/helm-charts&lt;/a> github repository and more in &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/install-pmm/install-pmm-server/deployment-options/helm/index.html" target="_blank" rel="noopener noreferrer">our documentation&lt;/a>.&lt;/p>
&lt;p>To deploy PMM3 in a namespace or environment with strict security (like OpenShift), you need to pass similar security context parameters. You can find how in &lt;a href="https://github.com/spron-in/blog-data/blob/master/pmm3-rootless/05.pmm3-helm.yaml" target="_blank" rel="noopener noreferrer">05.pmm3-helm.yaml&lt;/a> values manifest.&lt;/p>
&lt;p>Then the deployment will look like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">helm repo update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm install pmm3 percona/pmm -f 05.pmm3-helm.yaml --namespace secure-namespace&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>PMM 3’s rootless design excels where PMM 2 falters in secure Kubernetes environments. Enforcing Pod Security Standards, we saw PMM 3 deploy successfully, while PMM 2 failed, even with security contexts. This highlights PMM 3’s enhanced security, crucial for modern, hardened deployments. Using Helm simplifies secure PMM 3 deployments, ensuring robust database monitoring without compromising security.&lt;/p>
&lt;p>Try out &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/release-notes/3.0.0.html" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management version 3&lt;/a>, 100% open source database observability solution, and learn more about its enhancements.&lt;/p>
&lt;p>Tell us what you think in &lt;a href="https://forums.percona.com/c/percona-monitoring-and-management-pmm/pmm-3/84/l/new" target="_blank" rel="noopener noreferrer">our forum&lt;/a> or let us know if you are looking for &lt;a href="https://www.percona.com/about/contact" target="_blank" rel="noopener noreferrer">commercial support&lt;/a>.&lt;/p></content:encoded><author>Sergey Pronin</author><category>PMM</category><category>HELM</category><category>Docker</category><media:thumbnail url="https://percona.community/blog/2025/02/pmm3-rootless_hu_74de63da6cea5681.jpg"/><media:content url="https://percona.community/blog/2025/02/pmm3-rootless_hu_f897b8c176da0f3f.jpg" medium="image"/></item><item><title>How to Use IAM Roles for Service Accounts (IRSA) with Percona Operator for MongoDB on AWS</title><link>https://percona.community/blog/2025/02/17/how-to-use-iam-roles-for-service-accounts-irsa-with-percona-operator-for-mongodb-on-aws/</link><guid>https://percona.community/blog/2025/02/17/how-to-use-iam-roles-for-service-accounts-irsa-with-percona-operator-for-mongodb-on-aws/</guid><pubDate>Mon, 17 Feb 2025 00:00:00 UTC</pubDate><description>Introduction Percona Operator for MongoDB is an open-source solution designed to streamline and automate database operations within Kubernetes. It allows users to effortlessly deploy and manage highly available, enterprise-grade MongoDB clusters. The operator simplifies both initial deployment and setup, as well as ongoing management tasks like backups, restores, scaling, and upgrades, ensuring seamless database lifecycle management.</description><content:encoded>&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a> is an open-source solution designed to streamline and automate database operations within Kubernetes. It allows users to effortlessly deploy and manage highly available, enterprise-grade MongoDB clusters.  The operator simplifies both initial deployment and setup, as well as ongoing management tasks like backups, restores, scaling, and upgrades, ensuring seamless database lifecycle management.&lt;/p>
&lt;p>When running database workloads on Amazon EKS (Elastic Kubernetes Service), backup and restore processes often require access to AWS services like S3 for storage. A key challenge is ensuring these operations have secure, least-privileged access to AWS resources without relying on static credentials. Properly managing these permissions is crucial to maintaining data integrity, security, and compliance in automated backup and restore workflows.&lt;/p>
&lt;p>&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" target="_blank" rel="noopener noreferrer">IAM Roles for Service Accounts (IRSA)&lt;/a> is the recommended approach to solve this problem. IRSA allows Kubernetes pods to securely assume IAM roles, eliminating the need for hardcoded credentials, long-lived AWS keys, or excessive permissions. Instead, it leverages OpenID Connect (OIDC) authentication, ensuring that only the right workloads get access to AWS services.&lt;br>
By implementing IRSA, you enhance the security posture of your Kubernetes workloads while simplifying IAM management. In this article, we’ll walk through how IRSA works, why it’s beneficial, and how to configure it properly for the Percona Operator for MongoDB in EKS clusters.&lt;/p>
&lt;h1 id="irsa-installation-and-configuration-for-percona-operator-for-mongodb">IRSA Installation and Configuration for Percona Operator for MongoDB&lt;/h1>
&lt;ol>
&lt;li>IRSA requires an OpenID Connect (OIDC) provider associated with your EKS cluster.&lt;br>
So, you should &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html#:~:text=To%20create%20a%20provider%2C%20choose,com%20and%20choose%20Add%20provider." target="_blank" rel="noopener noreferrer">create an OIDC provider for your EKS cluster&lt;/a>. &lt;/li>
&lt;/ol>
&lt;p>Creating an OIDC provider for your EKS cluster involves several steps. This setup allows your EKS cluster to use IAM roles for service accounts, which makes it possible to grant fine-grained IAM permissions to pods.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Check if OIDC is already set up:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws eks describe-cluster --name &lt;cluster_name> --query &lt;span class="s2">"cluster.identity.oidc.issuer"&lt;/span> --output text
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://oidc.eks.eu-west-3.amazonaws.com/id/7AA1C67941083331A80382E464EB2F1F
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># If it is not already set up, create an OIDC provider:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">eksctl utils associate-iam-oidc-provider --region &lt;region> --cluster &lt;cluster-name> --approve&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here oidc-id is 7AA1C67941083331A80382E464EB2F1F. We will use it under role creation.&lt;/p>
&lt;ol start="2">
&lt;li>Create an IAM Policy to access s3 buckets.  Substitute &lt;s3_bucket> with your correct bucket name:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define the required permissions in an IAM policy JSON file: &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat s3-bucket-policy.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Version"&lt;/span>: &lt;span class="s2">"2012-10-17"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Statement"&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Effect"&lt;/span>: &lt;span class="s2">"Allow"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Action"&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"s3:*"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Resource"&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"arn:aws:s3:::&lt;s3_bucket>"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"arn:aws:s3:::&lt;s3_bucket>/*"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create the IAM policy:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws iam create-policy --policy-name &lt;policy name> --policy-document file://s3-bucket-policy.json&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Create an IAM Role and Attach the Policy:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Role example. Replace &lt;account-id> with account id and &lt;oidc-id> with cluster’s OIDC ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat role-trust-policy.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Version"&lt;/span>: &lt;span class="s2">"2012-10-17"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Statement"&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Effect"&lt;/span>: &lt;span class="s2">"Allow"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Principal"&lt;/span>: &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Federated"&lt;/span>: &lt;span class="s2">"arn:aws:iam::&lt;account-id>:oidc-provider/oidc.eks.&lt;region>.amazonaws.com/id/&lt;oidc-id>"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Action"&lt;/span>: &lt;span class="s2">"sts:AssumeRoleWithWebIdentity"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"Condition"&lt;/span>: &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"StringEquals"&lt;/span>: &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"oidc.eks.&lt;region>.amazonaws.com/id/&lt;oidc-id>:aud"&lt;/span>: &lt;span class="s2">"sts.amazonaws.com"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create role:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws iam create-role --role-name &lt;role_name> --assume-role-policy-document file://role-trust-policy.json --description &lt;span class="s2">"Allow access to s3 bucket"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Attach the policy to the role.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Please update &lt;role-name>, &lt;account-id> and &lt;policy-name> with the corresponding values.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws iam attach-role-policy --role-name &lt;role-name> --policy-arn arn:aws:iam::&lt;account-id>:policy/&lt;policy-name>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="5">
&lt;li>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/eks.html#install-the-operator-and-deploy-your-mongodb-cluster" target="_blank" rel="noopener noreferrer">Install the operator and deploy Percona Server for MongoDB&lt;/a> in your EKS cluster (skip this step if you already have the operator and the database cluster installed).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To ensure proper functionality, we need to annotate both the operator service account (default: percona-server-mongodb-operator) and the cluster service account (default: default).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>🔴 Warning: The cluster and operator  won’t restart automatically; therefore, a manual restart is necessary to apply the changes.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Get service accounts:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get sa -n &lt;namespace>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME SECRETS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">default &lt;span class="m">0&lt;/span> 25m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona-server-mongodb-operator &lt;span class="m">0&lt;/span> 25m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Get role_arn:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws iam get-role --role-name &lt;role-name> --query &lt;span class="s2">"Role.Arn"&lt;/span> --output text
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Annotate service account. Please update role_arn with appropriate value.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl annotate serviceaccount default &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> eks.amazonaws.com/role-arn&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;role_arn>"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl annotate serviceaccount percona-server-mongodb-operator &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> eks.amazonaws.com/role-arn&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;role_arn>"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="7">
&lt;li>To verify that the settings have been applied, inspect service accounts and the environment variables in both the operator and replica set (RS/Config) pods. The variable AWS_ROLE_ARN should be properly set.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Check annotation in service account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get sa -n &lt;namespace> percona-server-mongodb-operator -o yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get sa -n &lt;namespace> default -o yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Check the variable inside container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> -ti &lt;percona-server-mongodb-operator-container> -n &lt;operator_namespace> bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bash-5.1$ printenv &lt;span class="p">|&lt;/span> grep &lt;span class="s1">'AWS_ROLE_ARN'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">AWS_ROLE_ARN&lt;/span>&lt;span class="o">=&lt;/span>arn:aws:iam::1111111111111:role/some-name-psmdb-access-s3-bucket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> -ti &lt;rs0-0_pod> -n &lt;namespace> bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>mongodb@some-name-rs0-0 db&lt;span class="o">]&lt;/span>$ printenv &lt;span class="p">|&lt;/span> grep &lt;span class="s1">'AWS_ROLE_ARN'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">AWS_ROLE_ARN&lt;/span>&lt;span class="o">=&lt;/span>arn:aws:iam::1111111111111:role/some-name-psmdb-access-s3-bucket&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="8">
&lt;li>Configure the backup/restore settings as usual, but do not provide s3.credentialsSecret for the storage in deploy/cr.yaml. For detailed instructions  please refer to &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/backups-storage.html" target="_blank" rel="noopener noreferrer">Configure storage for backups&lt;/a>.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">shell&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># backup section in cr.yaml example &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storages:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> aws-s3:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: s3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> s3:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> region: &lt;region>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bucket: &lt;bucket>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;hr>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Using IAM Roles for Service Accounts (IRSA) in an Amazon EKS cluster is a best practice when running &lt;a href="https://docs.percona.com/percona-operators/" target="_blank" rel="noopener noreferrer">database operators&lt;/a> in Kubernetes. By integrating IRSA, database operators—such as the&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer"> Percona Server for MongoDB Operator&lt;/a>—can securely access AWS services like S3 for backups without relying on static credentials.&lt;/p>
&lt;p>IRSA enhances security by enforcing the principle of least privilege, ensuring that database operators in EKS have access only to the specific AWS resources they require. This approach reduces the risk of unauthorized access while also improving manageability by eliminating the need to store and rotate AWS credentials within Kubernetes secrets. By adopting IRSA in &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB Operator&lt;/a> , organizations can create a more secure, scalable, and automated environment for managing MongoDB databases.&lt;/p></content:encoded><author>Natalia Marukovich</author><category>Kubernetes</category><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2025/02/mongo-aws-iam_hu_784c39e43abdfe63.jpg"/><media:content url="https://percona.community/blog/2025/02/mongo-aws-iam_hu_2d794beb55bbadb2.jpg" medium="image"/></item><item><title>Join Percona for Google Summer of Code 2025 – Explore, Innovate, and Contribute!</title><link>https://percona.community/blog/2025/02/05/google-summer-of-code-2025/</link><guid>https://percona.community/blog/2025/02/05/google-summer-of-code-2025/</guid><pubDate>Wed, 05 Feb 2025 00:00:00 UTC</pubDate><description>Are you passionate about open-source databases, AI/ML, and security? Do you want to work on real-world projects that impact thousands of developers and enterprises worldwide? Percona is excited to invite students to participate in Google Summer of Code 2025 (GSoC) and help advance our cutting-edge open-source database solutions!</description><content:encoded>&lt;p>Are you passionate about open-source databases, AI/ML, and security? Do you want to work on real-world projects that impact thousands of developers and enterprises worldwide? Percona is excited to invite students to participate in &lt;a href="https://summerofcode.withgoogle.com/" target="_blank" rel="noopener noreferrer">&lt;strong>Google Summer of Code 2025 (GSoC)&lt;/strong>&lt;/a> and help advance our cutting-edge open-source database solutions!&lt;/p>
&lt;h1 id="why-contribute-to-percona">Why Contribute to Percona?&lt;/h1>
&lt;p>At Percona, we believe that &lt;strong>open world is a better world&lt;/strong>! GSoC is an excellent opportunity to work with seasoned developers, gain hands-on experience, and contribute to powerful database tools used by businesses globally.&lt;/p>
&lt;p>For 2025, we’re especially interested in projects that focus on &lt;strong>AI/ML&lt;/strong> and &lt;strong>security&lt;/strong>—two critical areas shaping the future of databases. Whether you’re passionate about &lt;strong>automating database performance insights&lt;/strong> with AI or &lt;strong>hardening security for mission-critical data&lt;/strong>, we have exciting challenges for you!&lt;/p>
&lt;p>Percona mentors are going to help you realize your ideas or one of the ideas below. We invite you to contribute to products and projects such as:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/percona-server" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/percona-postgresql-operator" target="_blank" rel="noopener noreferrer">Percona Operator for PostgreSQL&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">Percona Everest&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">pg_tde: Transparent Database Encryption for PostgreSQL&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>As well as CI/CD related projects with the Percona Build Engineering.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="project-ideas-for-gsoc-2025">Project Ideas for GSoC 2025&lt;/h1>
&lt;p>Below are some suggested project ideas categorized by Percona software:&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql">Percona Distribution for PostgreSQL&lt;/h2>
&lt;h3 id="snapshot-based-postgresql-backups">Snapshot-based PostgreSQL backups&lt;/h3>
&lt;p>Database users are often very familiar with their storage provider’s storage snapshot capabilities. These snapshots are very handy and performant to use, hence their popularity among users. Backups for other databases (e.g., MongoDB) are often configured via this capability as it provides many performance benefits for large-scale data, especially on Cloud deployments. Having such technology supported across the backup solutions for multiple databases makes it possible to leverage this effectively for Percona Everest via the Percona Operators.&lt;/p>
&lt;p>&lt;a href="https://www.crunchydata.com/blog/postgresql-snapshots-and-backups-with-pgbackrest-in-kubernetes" target="_blank" rel="noopener noreferrer">Comments from Crunchy&lt;/a> on what needs to be glued together to get snapshots and pgBackRest working together better. Additionally, &lt;a href="https://www.timescale.com/blog/making-postgresql-backups-100x-faster-via-ebs-snapshots-and-pgbackrest" target="_blank" rel="noopener noreferrer">Timescale on how they use snapshots and pgBackRest together in their hosted managed service&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Deliverables&lt;/strong>:
Have an API available to Percona Distribution for PostgreSQL to effectively use storage snapshots to create backups and restore from storage snapshot-based backups. Preferably, have it added to/complementary to the currently recommended solution of pgBackRest.&lt;/p>
&lt;p>Have Percona Operator for PostgreSQL expose the storage snapshot-based backup/restore so that Percona Everest can leverage it.&lt;/p>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++, PostgreSQL, Kubernetes
&lt;strong>Duration:&lt;/strong> 350 hours
**Difficulty level: **Hard
&lt;strong>Mentors:&lt;/strong> @Andrew_Pogrebnoi, @Jan_Wieremjewicz
&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-postgresql-operator" target="_blank" rel="noopener noreferrer">GitHub - percona/percona-postgresql-operator: Percona Operator for PostgreSQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">GitHub - percona/postgres: Percona Server for PostgreSQL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="pgbackrest-to-barman-close-gap-improvements">pgBackRest to Barman close gap improvements&lt;/h3>
&lt;p>PostgreSQL has two main backup tools: Barman and pgBackRest. Both are powerful backup and restore tools, each with its own strengths. pgBackRest is generally considered more advanced in terms of parallelism, performance, and flexibility. Barman does offer some advantages in certain areas. While pgBackRest is maintained by Community, Barman is a tool mainly maintained by one company and is less popular. Barman does have UX improvements over pgBackRest, especially for non-expert users:&lt;/p>
&lt;ul>
&lt;li>direct WAL archiving with PostgreSQL’s built-in archive_command,&lt;/li>
&lt;li>simpler backup and recovery process, especially for standby creation,&lt;/li>
&lt;li>clearer logging and monitoring for backup integrity,&lt;/li>
&lt;li>simpler configuration in small to medium deployments,&lt;/li>
&lt;li>better native tools for cloud backups&lt;/li>
&lt;/ul>
&lt;p>It would be beneficial for Percona, which uses pgBackRest in the Percona Distribution for PostgreSQL, to have the backup tool close any functionality gaps in Barman. Percona customers sometimes use Barman and expect Percona to support it. Having a way to migrate off Barman to pgBackRest, reducing any potential friction for the users, would be beneficial.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
Provide a close-gap set of improvements based on the list available in the description&lt;/p>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++, PostgreSQL
&lt;strong>Duration:&lt;/strong> 350 hours
&lt;strong>Difficulty level:&lt;/strong> Hard
&lt;strong>Mentors:&lt;/strong> @Andrew_Pogrebnoi, @Jan_Wieremjewicz
&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">GitHub - percona/postgres: Percona Server for PostgreSQL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="tool-to-investigate-postgresql-locks-for-dummies">Tool to investigate PostgreSQL locks for dummies&lt;/h3>
&lt;p>Currently, there is no tool that allows users with low experience to detect and understand all types of locks on their PG database, which may lead to many issues in deployments not managed by expert PostgreSQL users. As described in the blog posts below, understanding how locks work is difficult:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://xata.io/blog/anatomy-of-locks" target="_blank" rel="noopener noreferrer">Anatomy of Table-Level Locks in PostgreSQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://xata.io/blog/anatomy-of-locks-reduce" target="_blank" rel="noopener noreferrer">Anatomy of table-level locks: Reducing locking impact&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Detect ddl with mixed strong locks and others. i.e. allow to review locked PIDs as pg_locks will not work&lt;/li>
&lt;li>Present all locks in a GUI&lt;/li>
&lt;li>(Streched) have the GUI integrated in PMM&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++, PostgreSQL
&lt;strong>Duration:&lt;/strong> 350 hours
&lt;strong>Difficulty level:&lt;/strong> Medium
**Mentors: **Kai Wagner, @Jan_Wieremjewicz
&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">GitHub - percona/postgres: Percona Server for PostgreSQL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="session-continuity-for-pgbouncer-for-the-zero-downtime-upgrades">Session continuity for PgBouncer for the zero downtime upgrades&lt;/h3>
&lt;p>Percona is looking to introduce zero downtime upgrades capability to the Percona Operator and later on to Percona Everest. The assumption is to base on pgBouncer and our HA solution utilizing the replica with the new database and a switch from the previous version to the new version.&lt;/p>
&lt;p>Such a solution provides a zero downtime upgrade capability and a Rollback capability. To provide true zero downtime major upgrades for the current Percona Distribution for PostgreSQL, there needs to be an improvement that takes over the switching of sessions between the databases: the previous version and the new version&lt;/p>
&lt;p>In the future, this tool should also make it possible to zero downtime and migrate to Everest.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
Extend pgBouncer to ensure that the sessions can be switched between the databases without downtime for the users but only a potential performance drop&lt;/p>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++, Kubernetes
&lt;strong>Duration:&lt;/strong> 175 hours
&lt;strong>Difficulty leve&lt;/strong>l: Hard
**Mentors: **Kai Wagner, @Jan_Wieremjewicz
&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">GitHub - percona/postgres: Percona Server for PostgreSQL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="percona-software-for-mongodb">Percona Software for MongoDB&lt;/h2>
&lt;h3 id="interactive-shell-installer-for-percona-software-for-mongodb">Interactive Shell Installer for Percona Software for MongoDB&lt;/h3>
&lt;p>This project aims to develop an interactive shell-based installer for &lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> and &lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB&lt;/a>. The installer will simplify the installation, configuration, and initial setup process, making it easy for users to deploy these open-source enterprise solutions efficiently. The primary goal is to enhance the user experience by reducing manual setup steps and ensuring proper configuration through guided prompts and automation.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A command-line-based interactive installer script.&lt;/li>
&lt;li>Automated dependency checks and installation.&lt;/li>
&lt;li>Interactive prompts for configuration choices (e.g., authentication, replication, sharding).&lt;/li>
&lt;li>Seamless installation of both Percona Server for MongoDB and Percona Backup for MongoDB.&lt;/li>
&lt;li>Integration with package managers for major Linux distributions (Debian, Ubuntu, RHEL, CentOS).&lt;/li>
&lt;li>Logging and validation mechanisms to ensure correct setup.&lt;/li>
&lt;li>Documentation and user guide for the installer.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++ or Go&lt;/p>
&lt;p>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/p>
&lt;p>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/p>
&lt;p>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/p>
&lt;p>&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/p>
&lt;h3 id="percona-backup-for-mongodb-backup-speed-throttling">Percona Backup for MongoDB backup speed throttling&lt;/h3>
&lt;p>On large scale deployments, backups may significantly impact network performance - speficially network bandwidth may be heavily utilized, if the backup storage is fast, causing performance degradation of the database itself. Database reliability engineers would like to reduce the network load by slowing down physical backups with Percona Backup for MongoDB (PBM) configuration. The scope of the project is to implement a network bandwidth rate limiter and perform load testing showing the impact or rate limiting on backup time.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
The expected outcome of this project is insurance that backup will not degrade network bandwidth impacting the database. As a result a participant needs to provide proposed code changes in a form of fork of PBM and a create a report with load test results.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Go&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Duration:&lt;/strong> 175 hours&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Difficulty level:&lt;/strong> Easy&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-server-mongodb&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="percona-backup-for-mongodb-golang-sdk">Percona Backup for MongoDB Golang SDK&lt;/h3>
&lt;p>The project’s purpose is to extend capabilities and reduce maintenance in monitoring, managing, and automating backup and restores of MongoDB from &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a> tool. In the scope of the project there’s a migration from &lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB&lt;/a> CLI to a dedicated PBM golang client library in PMM. The client library (aka SDK) has to be implemented and map all current CLI operations to Go API functions. As a stretch goal, the project can be extended into implementing a backup progress reporting using the created SDK.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
The expected outcome of the project is the reduced maintenance of backup integration in PMM project and enabling extensibility of backup management. As a result of the project a new open-source SDK should be create&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Go&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Easy&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repository and resources:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/pmm" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="ceph-storage-support-in-percona-backup-for-mongodb">CEPH Storage support in Percona Backup for MongoDB&lt;/h3>
&lt;p>Ceph is an open source Software Defined Storage(SDS) software that is massively scalable and reliable. It’s one of the most popular storage technologies in Kubernetes and Openshift. The project aims to enable users to store their Percona Backups for MongoDB data on a Ceph storage which would be very convenient as they wouldn’t need to manage other additional storages. The scope of the project includes building a workspace setup on Kubernetes and &lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>, research on current challanges using Ceph storage, and implement necessary changes to make it work in a performant way. At the end document the solution.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
The project’s deliverables are technical documentation, Percona Operator for MongoDB changes, and instructions on setting up an environment with Ceph storage.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Go, Kubernetes&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 175 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Easy&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repository and resources:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-server-mongodb&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="boostfs-storage-support-in-percona-backup-for-mongodb">BoostFS storage support in Percona Backup for MongoDB&lt;/h3>
&lt;p>Dell Data Domain Boost File System (BoostFS) provides a general file system interface to the DD Boost library, allowing standard backup applications to take advantage of DD Boost features. In this project we’d like to extend our open-source Percona Backup for MongoDB to leverage that storage technology to reduce backup and restore time - and the same time help users to reduce their Recovery Time Objective (RTO) and Recovery Point Objective (RPO). In the scope of the project there’s a preparation of the workspace setup with Percona Server for MongoDB, Percona Backup for MongoDB and mounted BoostFS disk volumes on Google Cloud Platform and documenting the architecture of the environment. Additionally, the project includes a research on how PBM works with that storage and implementing necessary changes to PBM to make it work. Finally, a simple benchmark should be performed that proves the performance boost.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
It is expected that project delivers an architecture diagram of the testing environment in Google Cloud Platform, implementation of required changes to support BoostFS in PBM, and report incl. performance benchmark results and comparison to other storage systems.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Go, GCP&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Hard&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repository and resources:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://infohub.delltechnologies.com/en-us/l/dell-apex-block-storage-for-aws-backup-and-recovery-using-ddve-and-dd-boost-oracle-rman-agent/backup-procedure/" target="_blank" rel="noopener noreferrer">https://infohub.delltechnologies.com/en-us/l/dell-apex-block-storage-for-aws-backup-and-recovery-using-ddve-and-dd-boost-oracle-rman-agent/backup-procedure/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.dell.com/support/manuals/pl-pl/dd-virtual-edition/dd_p_ddve-gcp_ig/purpose-of-this-guide?guid=guid-015a004c-0518-4a23-a043-39c97ed165f0&amp;lang=en-us" target="_blank" rel="noopener noreferrer">https://www.dell.com/support/manuals/pl-pl/dd-virtual-edition/dd_p_ddve-gcp_ig/purpose-of-this-guide?guid=guid-015a004c-0518-4a23-a043-39c97ed165f0&amp;lang=en-us&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="openstack-swift-storage-support-in-percona-backup-for-mongodb">OpenStack Swift storage support in Percona Backup for MongoDB&lt;/h3>
&lt;p>The OpenStack Object Store project, known as Swift, offers cloud storage software so that you can store and retrieve lots of data with a simple API. It’s built for scale and optimized for durability, availability, and concurrency across the entire data set. Swift is ideal for storing unstructured data that can grow without bound. Swift is very convenient to use as a backup storage for MongoDB workloads running on OpenStack platform. The scope of the project includes building a workspace environment on Google Cloud Platform with OpenStack clusters and running there Percona Server for MongoDB. Then implementing required changes in Percona Backup for MongoDB to support Swift storage. Finally, performing benchmark tests and comparison to GCP native storages.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
It is expected that project delivers an architecture diagram of the testing environment in Google Cloud Platform, implementation of required changes to support OpenStack Swift in PBM, and report incl. performance benchmark results and comparison to other storage systems.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Go, GCP, OpenStack&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 175 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repositories and resources:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/openstack/swift" target="_blank" rel="noopener noreferrer">https://github.com/openstack/swift&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/ncw/swift" target="_blank" rel="noopener noreferrer">https://github.com/ncw/swift&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/kubernetes-engine/distributed-cloud/bare-metal/docs/installing/openstack" target="_blank" rel="noopener noreferrer">https://cloud.google.com/kubernetes-engine/distributed-cloud/bare-metal/docs/installing/openstack&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="percona-server-for-mysql-and-percona-xtradb-cluster">Percona Server for MySQL and Percona XtraDB Cluster&lt;/h2>
&lt;h3 id="automating-code-merges-with-ai">Automating Code Merges with AI&lt;/h3>
&lt;p>The regular and manual merge from Oracle’s GitHub repository process is time-consuming, complex, and prone to errors, particularly due to merge conflicts. Careful attention is required to avoid introducing regressions into Percona’s open-source products. While this project is specific to Percona’s needs, it addresses a common challenge in open-source software development, as many projects rely on upstream repositories for their code. Therefore, the solution can be generalized and could benefit other open-source projects with similar code integration needs.&lt;/p>
&lt;p>This GSoC project aims to develop an intelligent system using Artificial Intelligence to automate the MySQL fork merge process. Percona has been performing these merges for 18 years, accumulating a wealth of historical data (code changes, merge resolutions, conflict histories, test results) that can be leveraged to train an AI model.&lt;/p>
&lt;p>The core objective is to create a tool that can:&lt;/p>
&lt;ul>
&lt;li>Analyze upstream changes: Process and understand the changes introduced by Oracle in their MySQL repository.&lt;/li>
&lt;li>Identify merge conflicts: Identify conflicts between upstream changes and Percona’s modifications.&lt;/li>
&lt;li>Suggest merge resolutions: Propose solutions for resolving identified conflicts, drawing on patterns from historical merge data.&lt;/li>
&lt;li>Automate merges: Automatically apply upstream changes with the suggested merge resolutions.&lt;/li>
&lt;li>Learn and adapt: Continuously improve its performance and accuracy by learning from new merge data and feedback.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Reduced merge time and effort: Automating the merge process will free up developer time for other critical tasks.&lt;/li>
&lt;li>Improved merge accuracy: AI can potentially identify subtle conflicts that might be missed by manual review.&lt;/li>
&lt;li>Faster release cycles: Streamlining the merge process will enable quicker releases of updated Percona products.&lt;/li>
&lt;li>Open-source contribution: The resulting tool will be open-sourced, benefiting other projects that maintain forks of MySQL or similar databases. This problem is not unique to Percona; other open-source projects facing similar merging challenges can utilize this solution.&lt;/li>
&lt;/ul>
&lt;p>As a result of this project, you’re expected to deliver:&lt;/p>
&lt;ul>
&lt;li>A working prototype of the AI-powered merge tool.&lt;/li>
&lt;li>Well-documented code and training data.&lt;/li>
&lt;li>Comprehensive test suite and evaluation results.&lt;/li>
&lt;li>A report detailing the project’s methodology, findings, and future directions.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Python, Machine Learning libraries and frameworks (e.g., TensorFlow, PyTorch, scikit-learn), C++, Git, database systems
&lt;strong>Duration:&lt;/strong> 350 hours
&lt;strong>Difficulty level:&lt;/strong> Hard
&lt;strong>Mentors:&lt;/strong> Julia Vural, Oleksiy Lukin
&lt;strong>Relevant repositories and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-server" target="_blank" rel="noopener noreferrer">GitHub - percona/percona-server: Percona Server&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">GitHub - percona/percona-xtradb-cluster: A High Scalability Solution for MySQL Clustering and High Availability&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mysql/mysql-servermetal/docs/installing/openstack" target="_blank" rel="noopener noreferrer">https://github.com/mysql/mysql-servermetal/docs/installing/openstack&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="percona-everest">Percona Everest&lt;/h2>
&lt;h3 id="easier-troubleshooting-on-database-clusters-in-percona-everest">Easier Troubleshooting on database clusters in Percona Everest&lt;/h3>
&lt;p>The main goal is to provide tools to Percona Everest users to troubleshoot database clusters. This project will require the implementation of log collection, rotation, UI, and possibly an AI helper to analyze those logs. If users have a centralized log collection implemented, this tool needs to be able to integrate with it.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;p>Full user flow to support database cluster troubleshooting process (UI, backend, API, integrations).&lt;/p>
&lt;ul>
&lt;li>Log Collection &amp; Rotation System:
&lt;ul>
&lt;li>Implement a mechanism to collect logs from Percona Everest-managed database clusters.&lt;/li>
&lt;li>Ensure efficient log rotation to manage storage and performance impact.&lt;/li>
&lt;li>Enable compatibility with external log aggregation tools (e.g., Elasticsearch, Grafana Loki, or OpenTelemetry)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>User Interface for Log Access:
&lt;ul>
&lt;li>Develop a UI within Percona Everest to allow users to view and analyze logs.&lt;/li>
&lt;li>Include search, filtering, and visualization options for better troubleshooting.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>AI-Powered Log Analysis (Stretched scope)
&lt;ul>
&lt;li>Explore AI-driven log analysis to provide users with insights, anomaly detection, and recommendations.&lt;/li>
&lt;li>Implement basic AI-assisted troubleshooting if feasible within the project timeline.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Documentation &amp; Testing:
&lt;ul>
&lt;li>Deliver user and developer documentation covering installation, usage, and troubleshooting.&lt;/li>
&lt;li>Include test cases and automation scripts to ensure system reliability.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Kubernetes, Go, CI/CD
&lt;strong>Duration:&lt;/strong> 350 hours
**Difficulty level: **Medium
&lt;strong>Mentors:&lt;/strong> @Diogo_Recharte, @Mayank_Shah
&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">https://github.com/percona/everest&lt;/a>&lt;/p>
&lt;h3 id="percona-everest-rbac-policies-management-ui">Percona Everest RBAC policies management UI&lt;/h3>
&lt;p>Create a user interface to create and manage role-based access control policies&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Role-Based Access Control (RBAC) UI:
&lt;ul>
&lt;li>Develop a user-friendly interface in Percona Everest to create, update, and manage RBAC policies.&lt;/li>
&lt;li>Implement role assignment and permission configuration for database clusters.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Documentation &amp; Testing:
&lt;ul>
&lt;li>Deliver comprehensive user and developer documentation.&lt;/li>
&lt;li>Include test cases and automation scripts to ensure reliability.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Front-end, CI/CD tools
&lt;strong>Duration:&lt;/strong> 90 hours
&lt;strong>Difficulty level:&lt;/strong> Medium
&lt;strong>Mentors:&lt;/strong> @Diogo_Recharte, Peter Szczepaniak
&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">https://github.com/percona/everest&lt;/a>&lt;/p>
&lt;h3 id="context-sensitive-help">Context sensitive help&lt;/h3>
&lt;p>The Percona Everest documentation contains valuable information, hints, and tips, but we lack a way to present relevant information to our users. This project aims to work with the UX and Docs teams to solve this problem.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Implement a mechanism to display relevant documentation, hints, and tips based on the user’s current action or screen within Percona Everest.&lt;/li>
&lt;li>Ensure seamless integration with the existing UI for a non-intrusive experience.&lt;/li>
&lt;li>Enable contextual tooltips, pop-ups, or side panels that present relevant documentation without requiring users to leave the interface.&lt;/li>
&lt;li>Support links to full documentation pages when needed.&lt;/li>
&lt;li>Optionally, explore AI-driven suggestions based on user behavior and past queries.&lt;/li>
&lt;li>Allow users to control the level of help they receive (e.g., enable/disable tips, adjust verbosity).&lt;/li>
&lt;li>Provide user and developer documentation on how the system works and how to extend it.&lt;/li>
&lt;li>Ensure thorough testing to validate the accuracy and relevance of displayed help content.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Front-end, CI/CD tools
&lt;strong>Duration:&lt;/strong> 175 hours
&lt;strong>Difficulty level:&lt;/strong> Medium
&lt;strong>Mentors:&lt;/strong> @Diogo_Recharte, Peter Szczepaniak
&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">https://github.com/percona/everest&lt;/a>&lt;/p>
&lt;h3 id="backups-and-restore-timeline-visualization">Backups and restore timeline visualization&lt;/h3>
&lt;p>Databases are usually long-living services, and investigating issues with them is easier when you can see events like backups and restores of this service on a timeline.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Develop a visual timeline within Percona Everest to display backup and restore events for database clusters.&lt;/li>
&lt;li>Ensure the timeline is intuitive, zoomable, and supports different time ranges (e.g., last 24 hours, 7 days, custom range).&lt;/li>
&lt;li>Retrieve and display backup and restore events from Percona Everest’s database and logs.&lt;/li>
&lt;li>Include metadata such as timestamps, duration, status (success, failure), and associated users or processes.&lt;/li>
&lt;li>Allow users to filter events by type (full backup, incremental backup, restore, etc.).&lt;/li>
&lt;li>Enable color-coding or icons to differentiate event types at a glance.&lt;/li>
&lt;li>Deliver comprehensive user and developer documentation.&lt;/li>
&lt;li>Ensure automated tests for data accuracy, UI performance, and usability.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Front-end, CI/CD tools
&lt;strong>Duration:&lt;/strong> 175 hours
&lt;strong>Difficulty level:&lt;/strong> Medium
&lt;strong>Mentors:&lt;/strong> @Diogo_Recharte, Peter Szczepaniak
&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">https://github.com/percona/everest&lt;/a>&lt;/p>
&lt;h3 id="refactor-test-automation-using-page-object-model">Refactor test automation using page object model&lt;/h3>
&lt;p>Our project currently has a functional end-to-end (E2E) UI test suite that ensures the stability and correctness of our application. However, the test suite does not follow the Page Object Model (POM) design pattern, making it harder to maintain, scale, and debug.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Restructure existing test automation to follow the Page Object Model (POM) design pattern.&lt;/li>
&lt;li>Ensure better separation of test logic and UI elements for improved maintainability.&lt;/li>
&lt;li>Implement modular and reusable page object classes for different UI components and workflows.&lt;/li>
&lt;li>Standardize naming conventions and best practices for test scripts.&lt;/li>
&lt;li>Improve error handling and logging to make test failures easier to diagnose.&lt;/li>
&lt;li>Ensure the refactored test suite runs efficiently in CI/CD pipelines.&lt;/li>
&lt;li>Validate test performance improvements and maintain test coverage.&lt;/li>
&lt;/ul>
&lt;p>Required/preferred skills: Playwright, Typescript, Kubernetes
Duration: 175 hours
Difficulty level: Medium
Mentors: @Diogo_Recharte, Tomislav_Plavcic, Edith Puclla
&lt;strong>Relevant repository and resources:&lt;/strong> &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">https://github.com/percona/everest&lt;/a>&lt;/p>
&lt;h2 id="percona-monitoring-and-management-pmm">Percona Monitoring and Management (PMM)&lt;/h2>
&lt;h3 id="queryable-backup-and-restore-of-percona-server-for-mongodb">Queryable backup and restore of Percona Server for MongoDB&lt;/h3>
&lt;p>The project aims to equip MongoDB Database Administrators with &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a> extension that allows to query data directly from a backup. It solves their pain when they want to inspect just a single document in a collection from a few Terabytes size backup. The time associated with downloading the snapshot, decompressing it, getting it running in a local MongoDB node, and finally running the query would be significant. Not only that, but there are obvious nontrivial costs — both monetary and operational — associated with having to quickly spin up new environments. In scope of the project there is a backend Go application server implementation to run on-demand ephemeral mongodb instance, load data from backup, and enable user to run a DB query from a UI interface.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
It’s expected to deliver source code changes to PMM in form of PMM fork that extends PMM functionality for queryable MongoDB backup. Specifically, it is expected to prepare a solution design, implementation, unit and/or integration tests based on a Docker environment, and documentation.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Go, MongoDB&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repository:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/pmm" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="design-engineering-for-pmm">Design Engineering for PMM&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a> is a long-standing open-source software, but its age also comes with some UX and UI debt. Facing new goals to help innovate PMM, the team is excited to look forward to starting “renovating the house” and swapping the GUI with a more modern one built in-house. We are looking for experts in design engineering to help with:&lt;/p>
&lt;ul>
&lt;li>Making/refining/cataloging UI components that we will need for QA and production;&lt;/li>
&lt;li>Creating functional prototypes ad hoc from written ideas or designs;&lt;/li>
&lt;li>Convert old PMM pages like for like into new PMM pages (with new UI).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Contributing with ideas to help make the code library more easy to contribute to;&lt;/li>
&lt;li>Contributing to the code library with at least one new component;&lt;/li>
&lt;li>Create at least one code prototype for one of the team’s ongoing ideas;&lt;/li>
&lt;li>Convert at least one existing PMM functionality page UI into the new UI.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> CI/CD, Git, Storybook, React, MUI, Figma&lt;/p>
&lt;p>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/p>
&lt;p>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/p>
&lt;p>&lt;strong>Mentor&lt;/strong>: @pedro.fernandes&lt;/p>
&lt;h3 id="pmm-ui-for-postgresql-backups---create-restore-check-monitor">PMM UI for PostgreSQL backups - create, restore, check, monitor&lt;/h3>
&lt;p>Backup management without UI is not an easy task for the users. Having a tool that could be a tool of choice for backup and restore management could provide a unification layer for multiple backup/restore tools as well as provide a very important value that’s often overlooked: backup monitoring.&lt;/p>
&lt;p>As it turns out, many DBAs are often worried about the state of their backups and make it a daily routine task to check how the backups they have configured are. As the environments scale, this task becomes increasingly tiresome. Also, having to check whether the backups they create are effective makes it another routine task that needs either extra automation scripting to run a restore or a manual task.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Extend PMM UI to add existing backups so that they can be monitored&lt;/li>
&lt;li>Extend PMM UI to create backups&lt;/li>
&lt;li>Create a tool to automate the backup testing (check whether the backups created are usable)&lt;/li>
&lt;li>Extend PMM to monitor and alert on backup irregularities&lt;/li>
&lt;li>Integrate the backup management with external schedulers like cron.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> C++, Go, PostgreSQL
&lt;strong>Duration:&lt;/strong> 350 hours
&lt;strong>Difficulty level:&lt;/strong> Hard
&lt;strong>Mentors:&lt;/strong> Kai Wagner, @Jan_Wieremjewicz
&lt;strong>Relevant repository and resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/postgres" target="_blank" rel="noopener noreferrer">GitHub - percona/postgres: Percona Server for PostgreSQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/pmm" target="_blank" rel="noopener noreferrer">GitHub - percona/pmm: Percona Monitoring and Management: an open source database monitoring, observability and management tool&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="llm-powered-test-scenario-generation-for-open-source-contributions">LLM-Powered Test Scenario Generation for Open Source Contributions&lt;/h3>
&lt;p>Open-source projects thrive on community contributions, but ensuring that each pull request (PR) has adequate test coverage is a major challenge. Many PRs introduce changes without proper regression tests, leading to bugs and unstable releases.&lt;/p>
&lt;p>This project aims to build an LLM-powered tool that analyzes code changes in GitHub PRs and automatically generates relevant test scenarios. Using an Open Source LLM (e.g., DeepSeek, Mistral, LLaMA), the system will identify impact areas, suggest missing test cases, and recommend regression tests based on past commits. The goal is to integrate this into GitHub workflows, enabling maintainers to quickly assess PR test coverage and guide contributors in writing better tests.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>The student will work on developing a system with the following features:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>PR Analysis &amp; Impact Assessment&lt;/li>
&lt;li>Extract and analyze code diffs in pull requests.&lt;/li>
&lt;li>Identify affected functions, dependencies, and modules.&lt;/li>
&lt;li>Predict impact areas using a dependency graph.&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Test Scenario Generation using LLMs&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Use an Open Source LLM (DeepSeek, Mistral, etc.) to generate test cases.&lt;/li>
&lt;li>Recommend unit tests, integration tests, and regression scenarios.&lt;/li>
&lt;li>Compare new tests with existing ones to detect gaps in coverage.&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>GitHub Bot for Automated Suggestions&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Implement a bot that comments on PRs with test recommendations.&lt;/li>
&lt;li>Provide interactive feedback to contributors and maintainers.&lt;/li>
&lt;li>Integrate with GitHub Actions for CI/CD automation.&lt;/li>
&lt;/ul>
&lt;ol start="5">
&lt;li>Regression Test Identification&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Identify existing test cases that need to be re-run.&lt;/li>
&lt;li>Suggest additional tests based on historical PRs and past bug reports.&lt;/li>
&lt;/ul>
&lt;ol start="6">
&lt;li>Evaluation Metrics &amp; Benchmarking&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Measure effectiveness by tracking missed bugs before/after integration.&lt;/li>
&lt;li>Collect feedback from maintainers and contributors.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Future Scope:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Extend beyond GitHub to GitLab, Bitbucket, and other version control systems.&lt;/li>
&lt;li>Support additional test types, such as security and performance tests.&lt;/li>
&lt;li>Implement self-learning mechanisms to improve accuracy over time.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Strong programming skills in Python or JavaScript, Experience with GitHub APIs &amp; Pull Request Workflows, Understanding of Machine Learning / LLMs (DeepSeek, Mistral, LLaMA, etc.), Familiarity with Software Testing &amp; QA Automation, Experience with CI/CD Pipelines &amp; GitHub Actions (Bonus).
&lt;strong>Duration:&lt;/strong> 350 hours
&lt;strong>Difficulty level:&lt;/strong> Hard
&lt;strong>Mentor:&lt;/strong> Peter Sirotnak, @vasyl.yurkovych&lt;/p>
&lt;h2 id="percona-build-engineering">Percona Build Engineering&lt;/h2>
&lt;h3 id="sboms-for-percona-database-software---mysql-postgresql-and-mongodb">SBOMs for Percona database software - MySQL, PostgreSQL, and MongoDB&lt;/h3>
&lt;p>A “software bill of materials” (SBOM) has emerged as a key building block in software security and software supply chain risk management. An SBOM is a nested inventory, a list of ingredients that comprise software components. The project aims to adapt Percona’s build pipelines to generate SBOMs for Percona Software for MySQL, PostgreSQL, and MongoDB. This will enable organizations using Percona software to be more secure and avoid software supply chain vulnerabilities that were very harmful in late 2020 with the discovery of the &lt;a href="https://www.csoonline.com/article/3601508/solarwinds-supply-chain-attack-explained-why-organizations-were-not-prepared.html" target="_blank" rel="noopener noreferrer">Solar Winds&lt;/a> cyberattack or later with the &lt;a href="https://en.wikipedia.org/wiki/Log4Shell" target="_blank" rel="noopener noreferrer">Log4j&lt;/a> security flaw.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
At the end of the project, a running staging pipeline in Jenkins and Trivy should produce complete SBOMs for Percona Server for MySQL, PostgreSQL, and MongoDB, Percona Backup for MongoDB, Percona Xtra Backup for MySQL. SBOMs are uploaded automatically to the Percona repository and are downloadable publicly. Additionally, technical documentation on how the process works is expected to be created.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> Jenkins, Trivy&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 175 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Easy&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @radoslaw.szulgo&lt;/li>
&lt;li>&lt;strong>Relevant repository and resources:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-backup-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-backup-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-server-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://trivy.dev/v0.33/docs/sbom/" target="_blank" rel="noopener noreferrer">https://trivy.dev/v0.33/docs/sbom/&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="evolving-cicd-automating-build-test-and-release-for-robust-software-delivery">Evolving CI/CD: Automating Build, Test, and Release for Robust Software Delivery&lt;/h3>
&lt;p>Continuous Integration and Continuous Deployment (CI/CD) pipelines are the backbone of modern software development, ensuring rapid, reliable, and repeatable delivery. However, many pipelines still operate in fragmented stages, where builds and tests are automated, but releases remain a manual or semi-automated process.&lt;/p>
&lt;p>This project aims to transform our CI/CD pipelines into a true end-to-end automated system, seamlessly integrating build, test, and release stages. By implementing best practices in CI/CD automation, we will ensure that only thoroughly tested software progresses to release, minimizing human intervention and reducing the risk of deployment failures.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>
The successful completion of this project will result in a fully automated and robust CI/CD pipeline that seamlessly integrates build, test, and release processes. The key outcomes will include:&lt;/p>
&lt;p>&lt;strong>Fully Automated CI/CD Pipeline&lt;/strong>&lt;/p>
&lt;p>A redesigned pipeline where builds, testing, and releases are interconnected and automated.
Code changes will automatically trigger builds, run tests, and, if successful, deploy releases without manual intervention.
Comprehensive Test Integration&lt;/p>
&lt;p>The pipeline will incorporate unit tests, integration tests, security scans, and other quality assurance mechanisms.
Ensuring that faulty builds do not reach production by enforcing test-driven deployment.
Automated Release Process&lt;/p>
&lt;p>A mechanism that automatically releases software only if all tests pass.
Versioning, tagging, and artifact management will be streamlined.
The release process will be documented and configurable for different environments (e.g., staging, production).
Infrastructure as Code (IaC) &amp; Deployment Automation&lt;/p>
&lt;p>&lt;strong>Documentation &amp; Guides&lt;/strong>&lt;/p>
&lt;p>Clear technical documentation detailing the new pipeline’s workflow and configuration.
A step-by-step guide for developers and DevOps engineers on how to use and extend the pipeline.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Required/preferred skills:&lt;/strong> CI/CDl like Jenkins, GitHub Actions, GitLab CI, or similar; Docker; Testing frameworks and automated deployment strategies; infrastructure as code (IaC) and cloud environments is a plus.&lt;/li>
&lt;li>&lt;strong>Duration:&lt;/strong> 350 hours&lt;/li>
&lt;li>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/li>
&lt;li>&lt;strong>Mentors&lt;/strong>: @Evgeniy_Patlan , @Vadim_Yalovets&lt;/li>
&lt;li>&lt;strong>Relevant repository:&lt;/strong> &lt;a href="https://github.com/Percona-Lab/jenkins-pipelines" target="_blank" rel="noopener noreferrer">https://github.com/Percona-Lab/jenkins-pipelines&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="build-automation-for-open-source-databases">Build Automation for Open-Source Databases&lt;/h3>
&lt;p>Building and maintaining multiple database forks—such as MySQL, MongoDB, and PostgreSQL—often involves redundant build scripts, leading to inefficiencies, inconsistencies, and maintenance overhead. Currently, each database has its own set of build scripts despite sharing many common steps.&lt;/p>
&lt;p>This project aims to develop a modular, extensible build system that allows for streamlined compilation and packaging of different database forks. The system will provide a flexible framework where users can select required modules, specify target OS distributions, and automate the build process with minimal configuration.&lt;/p>
&lt;p>By implementing a plugin-based architecture, this modular builder will simplify cross-database maintenance, reduce duplication, and improve consistency across different builds.&lt;/p>
&lt;p>&lt;strong>Deliverables:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Modular Build Framework – A reusable, pluggable system that dynamically selects required modules for MySQL, MongoDB, and PostgreSQL builds.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Multi-OS Support – Automated builds for multiple Linux distributions (Debian, Ubuntu, CentOS, RHEL) with configurable OS selection.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Automated Package Creation – DEB and RPM package generation with standardized versioning and tagging.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Configurable &amp; Scalable Builds – Easy customization of build parameters, allowing extension to new database forks or patches.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>CI/CD Integration – Optional support for Jenkins, GitHub Actions, or GitLab CI to enable fully automated builds.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Comprehensive Documentation – User and developer guides with example configurations for quick adoption and extension.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Required/preferred skills:&lt;/strong> Bash/Python, CMake, Makefiles, Autotools, Linux and packaging (DEB/RPM), dependency management, CD/CD tools are a plus&lt;/p>
&lt;p>&lt;strong>Duration:&lt;/strong> 175 hours&lt;/p>
&lt;p>&lt;strong>Difficulty level:&lt;/strong> Medium&lt;/p>
&lt;p>&lt;strong>Mentors&lt;/strong>: @Evgeniy_Patlan , @Vadim_Yalovets&lt;/p>
&lt;p>&lt;strong>Relevant repositories&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-server-mongodb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-xtradb-cluster&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-server&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>More ideas are coming soon!&lt;/em>&lt;/p>
&lt;p>Suggest your ideas in the comments of the post or on &lt;a href="%28https://forums.percona.com/t/google-summer-of-code-2025-project-ideas/36461%29">the forum&lt;/a>.&lt;/p>
&lt;hr>
&lt;p>GSoC isn’t just about working on predefined ideas—it’s about innovation! If you have a project idea that aligns with &lt;strong>Percona software, AI/ML, security, or database performance&lt;/strong>, submit your proposal, and our mentors will be happy to discuss it with you.&lt;/p>
&lt;p>&lt;strong>Do you have questions?&lt;/strong> Visit our &lt;a href="https://forums.percona.com/t/google-summer-of-code-2025-project-ideas/36461" target="_blank" rel="noopener noreferrer">Community Forum&lt;/a> or join our chat channels to connect with potential mentors.&lt;/p>
&lt;p>&lt;strong>Ready to get started?&lt;/strong> See our &lt;a href="https://forums.percona.com/t/google-summer-of-code-2025-contribution-guide/36420" target="_blank" rel="noopener noreferrer">Google Summer of Code 2025: Contribution guide&lt;/a>.&lt;/p>
&lt;p>See you in GSoC 2025!&lt;/p></content:encoded><author>Radoslaw Szulgo</author><category>PMM</category><category>Percona</category><category>Opensource</category><category>MongoDB</category><category>GSoC</category><media:thumbnail url="https://percona.community/blog/2025/02/gsoc-blog-post-cover_hu_81a494f59f256715.jpg"/><media:content url="https://percona.community/blog/2025/02/gsoc-blog-post-cover_hu_5736630b427157bd.jpg" medium="image"/></item><item><title>Percona Operator for MongoDB 1.19: Remote Backups, Auto-Generated Passwords, and More!</title><link>https://percona.community/blog/2025/01/31/percona-operator-for-mongodb-1.19-remote-backups-auto-generated-passwords-and-more/</link><guid>https://percona.community/blog/2025/01/31/percona-operator-for-mongodb-1.19-remote-backups-auto-generated-passwords-and-more/</guid><pubDate>Fri, 31 Jan 2025 00:00:00 UTC</pubDate><description>The latest release of the Percona Operator for MongoDB, version 1.19, is here. It brings a suite of enhancements designed to streamline your MongoDB deployments on Kubernetes. This release introduces a technical preview of remote file server backups, simplifies user management with auto-generated passwords, supports Percona Server for MongoDB 8.0, and includes numerous other improvements and bug fixes. Let’s dive into the details of what 1.19 has to offer.</description><content:encoded>&lt;p>The latest release of the &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>, &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/RN/Kubernetes-Operator-for-PSMONGODB-RN1.19.0.html" target="_blank" rel="noopener noreferrer">version 1.19&lt;/a>, is here. It brings a suite of enhancements designed to streamline your MongoDB deployments on Kubernetes. This release introduces a technical preview of remote file server backups, simplifies user management with auto-generated passwords, supports Percona Server for MongoDB 8.0, and includes numerous other improvements and bug fixes. Let’s dive into the details of what 1.19 has to offer.&lt;/p>
&lt;h2 id="remote-backups-with-network-file-system-technical-preview">Remote Backups with Network File System (Technical Preview)&lt;/h2>
&lt;p>Backing up your MongoDB data is crucial, and Percona Operator for MongoDB 1.19 introduces a powerful new option for backup storage: the filesystem type. This feature, currently in technical preview, allows you to leverage a remote file server, mounted locally as a sidecar volume, for your backups. This is particularly useful in environments with network restrictions that prevent the use of S3-compatible storage or for organizations using non-standard storage solutions that support the Network File System (NFS) protocol.&lt;/p>
&lt;h3 id="setting-up-remote-backups">Setting Up Remote Backups&lt;/h3>
&lt;p>To use this new capability, you’ll need to add your remote storage as a sidecar volume within the replsets section of your Custom Resource (and configsvrReplSet for sharded clusters). Here’s how:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">replsets:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sidecarVolumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: backup-nfs-vol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nfs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server: "nfs-service.storage.svc.cluster.local"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> path: "/psmdb-my-cluster-name-rs0"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, configure the mount point and sidecar volume name in the &lt;code>backup.volumeMounts&lt;/code> section:&lt;/p>
&lt;p>YAML:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">backup:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumeMounts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - mountPath: /mnt/nfs/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: backup-nfs-vol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Finally, set up a filesystem type storage in the backup.storages section, pointing it to the mount point:&lt;/p>
&lt;p>YAML:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">backup:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> enabled: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storages:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> backup-nfs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: filesystem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> filesystem:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> path: /mnt/nfs/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>See more in our &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/backups-storage.html#remote-file-server" target="_blank" rel="noopener noreferrer">documentation about this storage type&lt;/a>.&lt;/p>
&lt;h2 id="simplified-user-management-with-auto-generated-passwords">Simplified User Management with Auto-Generated Passwords&lt;/h2>
&lt;p>Managing user credentials just got easier. Percona Operator for MongoDB 1.19 enhances declarative management of custom MongoDB users by adding the ability to generate passwords automatically. Now, when defining a new user in your deploy/cr.yaml file, you can omit the reference to an existing Secret containing the password, and the Operator will handle the generation for you:&lt;/p>
&lt;p>YAML:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">users:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: my-user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db: admin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: clusterAdmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db: admin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: userAdminAnyDatabase
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db: admin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The Operator will create a Secret to store the generated password securely. It is important to note that the Secret will be created after the cluster is in the Ready state.&lt;/p>
&lt;p>Get the user credentials:
Find the Secret resource named &lt;cluster-name>-custom-user-secret
Get the user password with this one-liner:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl get secret my-cluster-name-custom-user-secret -o jsonpath='{.data.my-user}' | base64 -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can find more details on this automatically created Secret in our &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/users.html#custom-mongodb-roles" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;h2 id="percona-server-for-mongodb-80-support">Percona Server for MongoDB 8.0 Support&lt;/h2>
&lt;p>Staying up-to-date with the latest MongoDB versions is essential for performance and security. Percona Operator for MongoDB 1.19 now officially supports Percona Server for MongoDB 8.0, in addition to 6.0 and 7.0. This means you can leverage the latest features and improvements from MongoDB 8.0, combined with the enterprise-grade enhancements and open-source commitment of Percona Server for MongoDB.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="980" height="980" sizes="(max-width: 980px) 100vw, 980px" srcset="https://percona.community/blog/2025/01/operator-mongodb-8_hu_11a4bc14ff5e9879.webp 768w, https://percona.community/blog/2025/01/operator-mongodb-8_hu_2bed304b315b840e.webp 480w, https://percona.community/blog/2025/01/operator-mongodb-8_hu_ca8d5b0dddacc726.webp 980w" src="https://percona.community/blog/2025/01/operator-mongodb-8_hu_ca8d5b0dddacc726.webp" alt="Percona Server for MongoDB 8.0 Support" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Check out &lt;a href="https://www.percona.com/blog/percona-server-for-mongodb-8-0-most-performant-ever/" target="_blank" rel="noopener noreferrer">this blog post&lt;/a> to learn more about the features in MongoDB 8.0.&lt;/p>
&lt;h2 id="streamlined-aws-s3-access-with-iam-roles-for-service-accounts-irsa">Streamlined AWS S3 Access with IAM Roles for Service Accounts (IRSA)&lt;/h2>
&lt;p>Percona Operator for MongoDB 1.19 adds support for &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" target="_blank" rel="noopener noreferrer">IAM Roles for Service Accounts (IRSA)&lt;/a>, simplifying secure access to AWS S3 for backups on Amazon EKS. IRSA lets you grant granular S3 permissions to specific Pods via their associated Kubernetes service accounts. This approach ensures that only the Pods that require S3 access receive it, adhering to the principle of least privilege. Furthermore, each Pod can only access credentials linked to its service account, providing strong credential isolation. For enhanced security, all S3 access is tracked through AWS CloudTrail, enabling comprehensive auditability. All of this happens without the need to manually manage and distribute AWS credentials.&lt;/p>
&lt;p>Configuration Steps&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Create an IAM Role: Define an IAM role with S3 access permissions. See &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" target="_blank" rel="noopener noreferrer">AWS documentation&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Identify Service Accounts: The Operator uses percona-server-mongodb-operator and your cluster uses default (customizable in deploy/cr.yaml).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Annotate Service Accounts: Link the IAM role to both service accounts:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ kubectl -n &lt;cluster namespace> annotate serviceaccount default eks.amazonaws.com/role-arn: &lt;YOUR_IAM_ROLE_ARN> --overwrite
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl -n &lt;operator namespace> annotate serviceaccount percona-server-mongodb-operator eks.amazonaws.com/role-arn: &lt;YOUR_IAM_ROLE_ARN> --overwrite&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Configure S3 Storage: Set up S3 storage in deploy/cr.yaml without s3.credentialsSecret. The Operator will use IRSA.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Important: IRSA credentials take precedence over IAM instance profiles, and S3 credentials in a Secret override both.&lt;/p>
&lt;p>IRSA streamlines S3 access, enhancing security and manageability for your MongoDB backups on EKS. Learn more in our &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/backups-storage.html#automating-access-to-amazon-s3-based-on-iam-roles" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Percona Operator for MongoDB 1.19 delivers a significant step forward in simplifying and automating the management of your MongoDB clusters on Kubernetes. With features like remote backups, auto-generated passwords, and support for Percona Server for MongoDB 8.0, this release empowers you to deploy, manage, and scale your databases with greater ease and efficiency.&lt;/p>
&lt;p>We encourage you to explore the &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/RN/Kubernetes-Operator-for-PSMONGODB-RN1.19.0.html" target="_blank" rel="noopener noreferrer">full release notes&lt;/a> and try out the new features. As always, your feedback is invaluable to us. Please share your thoughts and contribute to the project on our &lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">GitHub repository&lt;/a> or our &lt;a href="https://forums.percona.com/c/mongodb/percona-kubernetes-operator-for-mongodb/29" target="_blank" rel="noopener noreferrer">Community Forum&lt;/a>.&lt;/p></content:encoded><author>Sergey Pronin</author><category>Kubernetes</category><category>MongoDB</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/01/operator-1-19_hu_6722f842e532b42a.jpg"/><media:content url="https://percona.community/blog/2025/01/operator-1-19_hu_61ed34aa6c8532c6.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 3.0.0-GA</title><link>https://percona.community/blog/2025/01/29/percona-monitoring-management-3-ga/</link><guid>https://percona.community/blog/2025/01/29/percona-monitoring-management-3-ga/</guid><pubDate>Wed, 29 Jan 2025 00:00:00 UTC</pubDate><description>We’re excited to announce the release of Percona Monitoring and Management (PMM) 3.0.0 GA.</description><content:encoded>&lt;p>We’re excited to announce the release of &lt;strong>Percona Monitoring and Management (PMM) 3.0.0 GA&lt;/strong>.&lt;/p>
&lt;p>The Percona Monitoring and Management (PMM) 3.0.0 release delivers major security and stability enhancements. Notable security improvements include rootless deployments and encryption of sensitive data, along with improved API authentication using Grafana service accounts. Deployment options have expanded with official ARM support and the ability to use Podman for rootless deployments, providing flexibility and better security. Additionally, the introduction of containerized architecture has increased stability, and a streamlined upgrade process ensures reliability and ease of maintenance.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4256" height="2120" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2025/01/PMM-3.0.0_hu_99f917bff581039e.webp 768w, https://percona.community/blog/2025/01/PMM-3.0.0_hu_10c8760aecf331d8.webp 480w, https://percona.community/blog/2025/01/PMM-3.0.0_hu_58a6cb69687cff8e.webp 1400w" src="https://percona.community/blog/2025/01/PMM-3.0.0_hu_58a6cb69687cff8e.webp" alt="Percona Monitoring and Management (PMM) 3.0.0" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>User experience has been significantly improved with more flexible monitoring configurations and UI-based upgrades for Podman installations. This release also includes new features such as monitoring for MongoDB 8.0 and integration with Watchtower for automated container updates. These enhancements aim to provide users with a more secure, stable, and user-friendly monitoring and management experience.&lt;/p>
&lt;h2 id="release-notes">Release notes&lt;/h2>
&lt;p>&lt;strong>To see the full list of changes, check out the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/release-notes/3.0.0.html" target="_blank" rel="noopener noreferrer">3.0.0 GA Release Notes&lt;/a>&lt;/strong>&lt;/p>
&lt;p>Percona Monitoring and Management (PMM) 3.0.0 Release Notes:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Security Enhancements&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Implementation of rootless deployments to enhance security.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Encryption of sensitive data to ensure information confidentiality.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Improved API authentication with Grafana service accounts, increasing access security.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Deployment Options&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Official PMM Client ARM support, allowing the use of PMM on ARM architecture devices.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Rootless deployments using Podman, providing flexibility and security.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Support for deployments using Helm, Docker, Virtual Appliance, and Amazon AWS for various use cases.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Stability Improvements&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Increased stability through containerized architecture, providing isolation and manageability.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Streamlined upgrade process, reducing the risk of failures during updates and enhancing reliability.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>User Experience&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Flexible monitoring configurations, allowing users to tailor the system to their needs.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>UI-based upgrades for Podman installations, making the update process more convenient and intuitive.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>New Features&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Monitoring for MongoDB 8.0, ensuring support for the latest database versions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Integration with Watchtower for automated container updates, simplifying management and keeping the system up-to-date.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>We invite you to install and try the new PMM 3.0&lt;/p>
&lt;p>&lt;strong>Quickstart guide&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/quickstart.html" target="_blank" rel="noopener noreferrer">Get started with PMM&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Multiple installation options&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/3/install-pmm/index.html" target="_blank" rel="noopener noreferrer">About PMM installation&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>Contact us on the &lt;a href="https://forums.percona.com/c/percona-monitoring-and-management-pmm/pmm-3/84" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a>.&lt;/p></content:encoded><author>Ondrej Patocka</author><category>PMM</category><category>General Availability</category><category>Monitoring</category><category>Percona</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2025/01/pmm-blog-post-cover_hu_157785b07ee52466.jpg"/><media:content url="https://percona.community/blog/2025/01/pmm-blog-post-cover_hu_dd1f0c6bd73fc043.jpg" medium="image"/></item><item><title>MySQL 8.4 Support in Percona Toolkit 3.7.0</title><link>https://percona.community/blog/2025/01/06/mysql-8.4-support-in-percona-toolkit-3.7.0/</link><guid>https://percona.community/blog/2025/01/06/mysql-8.4-support-in-percona-toolkit-3.7.0/</guid><pubDate>Mon, 06 Jan 2025 00:00:00 UTC</pubDate><description>Percona Toolkit 3.7.0 has been released on Dec 23, 2024. The main feature of this release is MySQL 8.4 support.</description><content:encoded>&lt;p>&lt;em>Percona Toolkit 3.7.0 has been released on &lt;strong>Dec 23, 2024&lt;/strong>. The main feature of this release is MySQL 8.4 support.&lt;/em>&lt;/p>
&lt;p>&lt;em>In this blog, I will explain what has been changed. A full list of improvements and bug fixes can be found in the &lt;em>&lt;a href="https://docs.percona.com/percona-toolkit/release_notes.html" target="_blank" rel="noopener noreferrer">&lt;em>release notes&lt;/em>&lt;/a>&lt;/em>.&lt;/em>&lt;/p>
&lt;p>TLDR;&lt;/p>
&lt;ul>
&lt;li>Replication statements in 8.4 are fully supported by the Percona Toolkit&lt;/li>
&lt;li>&lt;code>pt-slave-delay&lt;/code> has been deprecated.&lt;/li>
&lt;li>&lt;code>pt-slave-find&lt;/code> has been renamed to &lt;code>pt-replica-find&lt;/code>. The old name has been deprecated but exists in the repository as an alias of the &lt;code>pt-replica-find&lt;/code>.&lt;/li>
&lt;li>&lt;code>pt-slave-restart&lt;/code> has been renamed to &lt;code>pt-replica-restart&lt;/code>. Old name has been deprecated but exists in the repository as an alias of the &lt;code>pt-replica-restart&lt;/code>.&lt;/li>
&lt;li>Basic SSL support has been added to the tools where it was not working before (see &lt;a href="https://perconadev.atlassian.net/browse/PT-191" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PT-191&lt;/a> ), and Percona Toolkit now supports &lt;code>caching_sha2_password&lt;/code> and &lt;code>sha256_password&lt;/code>authentication plugins. Full implementation of &lt;a href="https://perconadev.atlassian.net/browse/PT-191" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PT-191&lt;/a> is planned for the next version.&lt;/li>
&lt;/ul>
&lt;h2 id="replication-statements">Replication Statements&lt;/h2>
&lt;p>MySQL 8.4 removed earlier deprecated offensive language, such as &lt;code>SLAVE&lt;/code> or &lt;code>MASTER&lt;/code>. This made tools written for earlier versions not compatible with the new version. Percona Toolkit was also affected, and I had to rewrite it.&lt;/p>
&lt;p>However, Percona Toolkit should be able to run not only with MySQL 8.4 but also with older versions. So, the change was not a simple grep and replace of offensive words. It is not even possible for version MySQL 8.0 because new syntax was first introduced in 8.0.23 for the &lt;code>CHANGE REPLICATION SOURCE&lt;/code> and &lt;code>START/STOP REPLICA&lt;/code> commands. Earlier versions weren’t aware of this change.&lt;/p>
&lt;p>Another challenge was the fact that I could replace all occurrences of the word &lt;code>SLAVE&lt;/code> with &lt;code>REPLICA&lt;/code>. Still, I could not do the same for the &lt;code>MASTER&lt;/code> and &lt;code>SOURCE&lt;/code> pairs because replication source-related commands are mapped differently:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>Legacy syntax&lt;/strong>&lt;/th>
&lt;th>&lt;strong>Syntax without offensive words&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>CHANGE MASTER&lt;/code>&lt;/td>
&lt;td>&lt;code>CHANGE REPLICATION SOURCE&lt;/code> (since 8.0.23)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SHOW MASTER STATUS&lt;/code>&lt;/td>
&lt;td>&lt;code>SHOW BINARY LOG STATUS&lt;/code> (since 8.4.0)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RESET MASTER&lt;/code>&lt;/td>
&lt;td>&lt;code>RESET BINARY LOGS[ AND GTIDS]&lt;/code> ( since 8.4.0)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>MASTER&lt;/code>  in other commands&lt;/td>
&lt;td>&lt;code>SOURCE&lt;/code> (partially since 8.0.23, fully since 8.4)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>So, I added selectors that use the correct command depending on the MySQL server version.&lt;/p>
&lt;p>I intentionally implemented new syntax for version 8.4 only, so I do not have to check every single minor version of 8.0. I also did not implement new syntax for MariaDB. This may happen in the future.&lt;/p>
&lt;p>&lt;em>&lt;strong>However, all messages displayed to the user use the new syntax. If you rely on old syntax somewhere in your scripts, adjust them.&lt;/strong>&lt;/em>&lt;/p>
&lt;p>Internally, most of the functions were renamed to use the new syntax, but the important module &lt;code>lib/MasterSlave.pm&lt;/code> kept its name.&lt;/p>
&lt;h2 id="deprecated-and-outdated-tools">Deprecated and Outdated Tools&lt;/h2>
&lt;p>As a result of this change, &lt;code>pt-slave-delay&lt;/code> has been deprecated. The tool stays in the repository and works as before when connected to MySQL 8.0 or an earlier version. However, it refuses to work with MySQL 8.4. The tool will be removed in one of the future versions.&lt;/p>
&lt;p>Tools &lt;code>pt-slave-find&lt;/code> and &lt;code>pt-slave-restart&lt;/code> were renamed to &lt;code>pt-replica-find&lt;/code> and &lt;code>pt-replica-restart&lt;/code>. Aliases with old names still exist, so you have time to change your scripts. However, expect that these aliases will be removed in one of the future versions as well.&lt;/p>
&lt;p>Tool &lt;code>pt-variable-advisor&lt;/code> has been updated to reflect current default values.&lt;/p>
&lt;h2 id="basic-ssl-support">Basic SSL Support&lt;/h2>
&lt;p>Percona Toolkit did not have consistent SSL support: some of the tools were able to connect using SSL, and others did not. This was reported at &lt;a href="https://perconadev.atlassian.net/browse/PT-191" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PT-191&lt;/a>. In this version, I added option “&lt;code>s&lt;/code>” for &lt;code>DSN&lt;/code> that instructs &lt;code>DBD::mysql&lt;/code> to open a secure connection with the database. As a result, Percona Toolkit now supports &lt;code>caching_sha2_password&lt;/code> and &lt;code>sha256_password&lt;/code> authentication plugins. But other SSL options are still missed. Full SSL support will be added in the next version.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Percona Toolkit fully supports MySQL 8.4. If you use &lt;code>pt-slave-find&lt;/code> and &lt;code>pt-slave-restart&lt;/code>, consider calling them by their new names &lt;code>pt-replica-find&lt;/code> and &lt;code>pt-replica-restart&lt;/code>. Tool &lt;code>pt-slave-delay&lt;/code> has been deprecated and will be removed in future versions. Use built-in feature &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/replication-delayed.html" target="_blank" rel="noopener noreferrer">delayed replication&lt;/a> instead.&lt;/p></content:encoded><author>Sveta Smirnova</author><category>Toolkit</category><category>MySQL</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2025/01/toolkit-370_hu_5b639c8b155c6c50.jpg"/><media:content url="https://percona.community/blog/2025/01/toolkit-370_hu_a2b76fa1e2f67fb9.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 3.0.0-Beta - Tech Preview</title><link>https://percona.community/blog/2024/12/02/percona-monitoring-management-technical-preview/</link><guid>https://percona.community/blog/2024/12/02/percona-monitoring-management-technical-preview/</guid><pubDate>Mon, 02 Dec 2024 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 3.0.0 Beta - Tech Preview We’re excited to announce the Tech Preview (Beta) release of Percona Monitoring and Management (PMM) 3.0.0-Beta.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-300-beta---tech-preview">Percona Monitoring and Management 3.0.0 Beta - Tech Preview&lt;/h2>
&lt;p>We’re excited to announce the Tech Preview (Beta) release of &lt;strong>Percona Monitoring and Management (PMM) 3.0.0-Beta&lt;/strong>.&lt;/p>
&lt;blockquote>
&lt;p>This release is intended for testing environments only, as it’s not yet production-ready. The GA (General Availability) release will be available through standard channels in the upcoming months.&lt;/p>&lt;/blockquote>
&lt;h2 id="release-notes">Release notes&lt;/h2>
&lt;p>&lt;strong>To see the full list of changes, check out the &lt;a href="https://pmm-doc-3.onrender.com/release-notes/3.0.0_Beta.html" target="_blank" rel="noopener noreferrer">3.0.0-Beta - Tech Preview Release Notes&lt;/a>&lt;/strong>&lt;/p>
&lt;h2 id="installation-options">Installation options&lt;/h2>
&lt;h3 id="pmm-server">PMM Server&lt;/h3>
&lt;h4 id="docker">Docker&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://hubgw.docker.com/r/perconalab/pmm-server/tags?name=3.0.0-beta" target="_blank" rel="noopener noreferrer">Server&lt;/a>: &lt;code>docker pull perconalab/pmm-server:3.0.0-beta&lt;/code>&lt;/li>
&lt;li>&lt;a href="https://pmm-doc-3.onrender.com/install-pmm/install-pmm-server/baremetal/docker/easy-install.html" target="_blank" rel="noopener noreferrer">Docker installation guide&lt;/a>&lt;/li>
&lt;/ul>
&lt;h4 id="vm">VM&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM3-Server-2024-11-26-1307.ova" target="_blank" rel="noopener noreferrer">Download OVA file&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pmm-doc-3.onrender.com/install-pmm/install-pmm-server/baremetal/virtual/index.html" target="_blank" rel="noopener noreferrer">VM Installation guide&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="pmm-client">PMM Client&lt;/h3>
&lt;h4 id="docker-images">Docker images&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://hubgw.docker.com/r/perconalab/pmm-client/tags?name=3.0.0-beta" target="_blank" rel="noopener noreferrer">AMD 64 + ARM 64&lt;/a>: &lt;code>docker pull perconalab/pmm-client:3.0.0-beta&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="binary-packages">Binary packages&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://downloads.percona.com/downloads/TESTING/pmm-client-3.0.0beta/pmm-client-3.0.0beta.AMD64.tar.gz" target="_blank" rel="noopener noreferrer">Download AMD 64 tarball&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://downloads.percona.com/downloads/TESTING/pmm-client-3.0.0beta/pmm-client-3.0.0beta.ARM64.tar.gz" target="_blank" rel="noopener noreferrer">Download ARM 64 tarball&lt;/a>&lt;/li>
&lt;/ul>
&lt;h4 id="package-manager-installation">Package Manager installation&lt;/h4>
&lt;ol>
&lt;li>Enable testing repository via Percona-release: &lt;code>percona-release enable pmm3-client testing&lt;/code>&lt;/li>
&lt;li>Install relevant pmm-client package using your system’s package manager&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>Contact us on the &lt;a href="https://forums.percona.com/c/percona-monitoring-and-management-pmm" target="_blank" rel="noopener noreferrer">Percona Community Forums&lt;/a>.&lt;/p></content:encoded><author>Ondrej Patocka</author><category>PMM</category><category>Technical Preview</category><category>Monitoring</category><category>Percona</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2024/12/pmm-blog-post-cover_hu_d535f2202891bf3f.jpg"/><media:content url="https://percona.community/blog/2024/12/pmm-blog-post-cover_hu_ab7fc16f44593397.jpg" medium="image"/></item><item><title>Percona Bug Report: October 2024</title><link>https://percona.community/blog/2024/11/25/percona-bug-report-october-2024/</link><guid>https://percona.community/blog/2024/11/25/percona-bug-report-october-2024/</guid><pubDate>Mon, 25 Nov 2024 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://jira.percona.com/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs,&lt;/p>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8057" target="_blank" rel="noopener noreferrer">PS-8057&lt;/a>: When max_slowlog_size is set to above 4096, then it gets reset to 1073741824. This overwrites the slow log file path with a different file name, which becomes like node_name.log.000001. Due to this issue, your path defined at slow_query_log_file won`t be useful. This issue has started happening since MySQL Version 8.0.32.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;p>MySQL 8.0.36 is running with the following set of configurations:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">slow_query_log = ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log_file = /home/user/sandboxes/msb_ps8_0_36/data/slow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">long_query_time = 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_slowlog_files = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_slowlog_size = 510000000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check the log_file path:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql [localhost:8036] {msandbox} ((none)) > show global variables like "%slow_query_log_file%";
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| slow_query_log_file | /home/adi/sandboxes/msb_ps8_0_36/data/localhost.log.000001 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Interestingly, you will see that this file,/home/user/sandboxes/msb_ps8_0_36/data/localhost.log, 000001, was not even created.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">user@localhost:~/sandboxes/msb_ps8_0_36/data$ ll | grep slow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r----- 1 adi adi 355518891 Aug 13 16:45 localhost-slow.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r----- 1 adi adi 1079653421 Jul 30 18:13 localhost-slow.log.old
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r----- 1 adi adi 255 Aug 13 16:48 slow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r----- 1 adi adi 255 Aug 13 16:45 slow.000001&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After removing max_slowlog_size = 510000000&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql [localhost:8036] {msandbox} ((none)) > show global variables like "%slow_query_log_file%";
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+--------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+--------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| slow_query_log_file | /home/adi/sandboxes/msb_ps8_0_36/data/slow |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+--------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 5.7.36-39, 8.0.35-27, 8.0.36-28&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.39-30, 8.4.2-2&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Use “set global slow_query_log_file =’&lt;correct slow query log file>’;”&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9214" target="_blank" rel="noopener noreferrer">PS-9214&lt;/a>: INPLACE ALTER TABLE might fail with a duplicate key error if concurrent insertions occur; there have been many bugs reported here and in MySQL bugs regarding duplicate key errors while doing an online alter table operation on tables with primary and unique keys indexes. The bug is not as easy to reproduce but involves ONLY the primary key and includes an atomic sequence that cannot create a duplicate.  It seems to be related to page splits/merges.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27, 8.0.36-28&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.39-30, 8.4.2-2&lt;/p>
&lt;p>&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=115511" target="_blank" rel="noopener noreferrer">115511&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Use ALTER TABLE … ALGORITHM=COPY instead.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9275" target="_blank" rel="noopener noreferrer">PS-9275&lt;/a>: When querying based on a function, MySQL does not use the available functional index when using the LIKE operator, which results inconsistent query plans when functional Indexes are used.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE `test` (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `id` int NOT NULL AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `a` varchar(200) DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `test02` varchar(9) GENERATED ALWAYS AS (monthname(from_unixtime(`a`))) VIRTUAL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `!hidden!test01!0!0` varchar(9) GENERATED ALWAYS AS (monthname(from_unixtime(`a`))) VIRTUAL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PRIMARY KEY (`id`),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> KEY `test01` ((monthname(from_unixtime(`a`)))),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> KEY `test02` (`test02`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> explain select MONTHNAME(FROM_UNIXTIME(a)) from test WHERE MONTHNAME(FROM_UNIXTIME(a)) Like 'April%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 13 | 100.00 | Using where |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set, 1 warning (0,00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql> explain select MONTHNAME(FROM_UNIXTIME(a)) from test WHERE `!hidden!test01!0!0` Like 'April%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | SIMPLE | test | NULL | range | test01 | test01 | 39 | NULL | 2 | 100.00 | Using where |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set, 1 warning (0,00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.36-28, 8.4.X&lt;/p>
&lt;p>&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=104713" target="_blank" rel="noopener noreferrer">104713&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Use the indexes created on virtual fields explicitly.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9286" target="_blank" rel="noopener noreferrer">PS-9286:&lt;/a> &lt;a href="https://docs.oasis-open.org/kmip/spec/v1.4/kmip-spec-v1.4.html#:~:text=Limits%20Attribute%20Rules-,3.22%20State,-This%20attribute%20is" target="_blank" rel="noopener noreferrer">KMIP&lt;/a> Component leaves keys in a pre-active state.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.X, 8.3.0-1, 8.4.0-1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.39-30, 8.4.2-2&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9314" target="_blank" rel="noopener noreferrer">PS-9314:&lt;/a> The database crashed due to the SELECT statement. Since the JSON is invalid, the command should return ERROR 3146, an Invalid data type for JSON, but unfortunately, it crashed the instance with Signal 11 using JSON_TABLE.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.36-28, 8.0.37-29, 8.0.39-30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.39-30, 8.4.2-2&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> show global variables like 'version%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+-----------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+-----------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version | 8.0.36-28 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version_comment | Percona Server (GPL), Release 28, Revision 47601f19 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version_compile_machine | x86_64 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version_compile_os | Linux |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version_compile_zlib | 1.2.13 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version_suffix | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+-----------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">6 rows in set (0.01 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql> SELECT ele AS domain FROM JSON_TABLE('["TEST'+(select load_file('test'))+'"]', "$[*]" COLUMNS (ele VARCHAR(70) PATH "$" )) AS json_elements ;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 2013 (HY000): Lost connection to MySQL server during query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No connection. Trying to reconnect...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Can't connect to the server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9369" target="_blank" rel="noopener noreferrer">PS-9369:&lt;/a> The audit plugin causes memory exhaustion after a few days; disconnecting threads and disabling the audit plugin is undesirable. This workaround can not be used since it requires scheduling an application outage. Even when small, it’s a recurrent event.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.37-29&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.40-31 [Yet to Release]&lt;/p>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4453" target="_blank" rel="noopener noreferrer">PXC-4453:&lt;/a> In 3 Node PXC cluster, node01 has active flow control(FC). Active FC blocks user sessions to insert a message into the channel queue (session waits on send monitor (conn->sm)); send monitor is blocked because FC is active. The idea behind the logic is that applier threads, when consuming messages from the queue conn->recv_q, should check if FC is active, and if the queue level is below conn->lower_limit, FC should be disabled, and the user connection thread waiting on the sending monitor should be woken up. In other words, disabling the FC signal is driven by the consumption of events from recv_q by applier threads.&lt;/p>
&lt;p>In this case, it seems that recv_q is empty, but FC is active, so nothing can be added to recv_q. We have a vicious circle of some kind of deadlock, and due to this race condition, we are seeing cluster hangs.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 5.7.25, 5.7.44, 8.0.36-28&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXC 8.0.37-29, 8.4.0&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4404" target="_blank" rel="noopener noreferrer">PXC-4404:&lt;/a> wsrep_preordered=ON causes protocol violations, which cause a node to crash when the group view changes on a cluster with a node acting as an async replica.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 5.7.44-31&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Set wsrep_preordered=OFF; however, you may experience a delay in async replication.&lt;/p>
&lt;p>Note: Option &lt;a href="https://galeracluster.com/library/documentation/mysql-wsrep-options.html#wsrep-preordered" target="_blank" rel="noopener noreferrer">wsrep_preordered&lt;/a> is deprecated in MySQL-wsrep: 8.0.19-26.3, MariaDB: 10.1.1&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4362" target="_blank" rel="noopener noreferrer">PXC-4362:&lt;/a> The PXC node evicted when creating a function by the user doesn`t have the super privilege, and binary logging is enabled.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.34-26&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXC 8.0.36-28, 8.4.0&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Setting log_bin_trust_function_creators is the workaround. Note that log_bin_trust_function_creators is deprecated by MySQL 8.0.34 and will be removed in the future.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4365" target="_blank" rel="noopener noreferrer">PXC-4365&lt;/a>: PXC nodes leave clusters when the row size is too large and have more than 3 nvarchar columns.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXC 8.0.36-28, 8.3.0&lt;/p>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2325" target="_blank" rel="noopener noreferrer">PT-2325&lt;/a>: pt-table-sync does not produce the correct SQL statements to sync tables containing JSON columns properly.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;p>pt-table-sync emits the following SQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DELETE FROM `test`.`test_to` WHERE `id`='2' AND `data`='{"baz": "quux"}' LIMIT 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO `test`.`test_to`(`id`, `data`) VALUES ('1', '{"foo": "bar"}');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The INSERT statement works fine, but the DELETE fails to delete the row with &lt;code>id&lt;/code>=‘2’, because the AND &lt;code>data&lt;/code>=’{“baz”: “quux”}’ portion of the WHERE clause will result in the query matching zero rows.&lt;/p>
&lt;p>Verify the incorrect contents of the test_to table with the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Examine the state of our test tables.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ docker exec -it mysql_5_7_12_test mysql -utest -ptest -e "
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> use test;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> select * from test_to;"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That should return the following output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+------+-----------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| id | data |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------+-----------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 2 | {"baz": "quux"} |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | {"foo": "bar"} |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------+-----------------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Witness that the row with id=2 still exists in the table and was not deleted as it should have been. With JSON columns, the DELETE statement would need to look like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DELETE FROM `test`.`test_to`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE `id`='2'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AND `data`=CAST('{"baz": "quux"}' AS JSON)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIMIT 1;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.7&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2329" target="_blank" rel="noopener noreferrer">PT-2329&lt;/a>: During the run, pt-archiver will ignore columns that are camelCase during the insert, but it will get all the columns during select.&lt;/p>
&lt;p>It could be confirmed by using a dry-run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pt-archiver --source [...] --dest [...] --where "1=1" --statistics --progress=10000 --limit=1000 --no-delete --no-safe-auto-increment --no-check-columns --columns=addressLine1,addressLine2,city,state,postalCode,country,customerNumber --why-quit --skip-foreign-key-checks --dry-run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here are the results:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SELECT /*!40001 SQL_NO_CACHE */ `addressLine1`,`addressLine2`,`city`,`state`,`postalCode`,`country`,`customerNumber`,`customernumber` FROM `classicmodels`.`customers` FORCE INDEX(`PRIMARY`) WHERE (1=1) ORDER BY `customernumber` LIMIT 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT /*!40001 SQL_NO_CACHE */ `addressLine1`,`addressLine2`,`city`,`state`,`postalCode`,`country`,`customerNumber`,`customernumber` FROM `classicmodels`.`customers` FORCE INDEX(`PRIMARY`) WHERE (1=1) AND ((`customernumber` > ?)) ORDER BY `customernumber` LIMIT 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO `classicmodels`.`addresses`(`city`,`state`,`country`) VALUES (?,?,?)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.7&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> The solution is to include all columns in lowercase in the param –columns until the bug is fixed.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2344" target="_blank" rel="noopener noreferrer">PT-2344&lt;/a>: pt-config-diff compares mysqld options, but it fails if the [mysqld] section is in uppercase, even though that is a valid way of setting mysqld variables. Since [MYSQLD] is acceptable for MySQL, pt-config-diff should compare the options under that section.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.7&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Use [mysqld] as lowercase until the bug is fixed.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2355" target="_blank" rel="noopener noreferrer">PT-2355&lt;/a>: Table data is lost if we accidentally resume a previously failed job that has null boundaries. pt-online-schema-change should not resume a job with empty boundaries.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.6.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PT 3.7.1&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Do not run pt-online-schema-change with job id having null boundaries.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2356" target="_blank" rel="noopener noreferrer">PT-2356&lt;/a>: If you run pt-online-schema-change, which results in an error, then subsequent runs will create new tables that won`t be cleaned up.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.6.0&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2349" target="_blank" rel="noopener noreferrer">PT-2349&lt;/a>: pt-table-sync is failing to sync data from PXC to the async environment, and trigger errors include “WSREP detected deadlock/conflict and aborted the transaction.”&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.3.1, 3.5.2, 3.6.0&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-1726" target="_blank" rel="noopener noreferrer">PT-1726&lt;/a>: pt-query-digest is not distinguishing queries when an alias is used&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.6.0&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;p>Queries from slow query log:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Time: 2019-01-31T11:00:00.728957Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># User@Host: sageone_ext_uk[sageone_ext_uk] @ [10.181.130.22] Id: 18714290
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Query_time: 2.709699 Lock_time: 0.000402 Rows_sent: 19 Rows_examined: 51011
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use sageone_ext_uk;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SET timestamp=1548932400;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT a,b,c from table1 as t1 where t1.a=3 and t1.b=5;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Time: 2019-01-31T11:00:00.728957Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># User@Host: sageone_ext_uk[sageone_ext_uk] @ [10.181.130.22] Id: 18714290
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Query_time: 2.709699 Lock_time: 0.000402 Rows_sent: 19 Rows_examined: 51011
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use sageone_ext_uk;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SET timestamp=1548932400;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT a,b,c from table1 as t1 where t1.a=3 and t1.c=5;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The fingerprints for the above queries are the same, which is incorrect behaviour:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">select a,b,c from table? as t? where t?=? and t?=?&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2374" target="_blank" rel="noopener noreferrer">PT-2374&lt;/a>: If we say –ignore=bob, every combination of the bob user will be ignored. This includes bob@localhost, bob@::1, bob@foobar, etc. But this is not the case. Only bob@% is ignored; pt-show-grants –ignore does not ignore all accounts.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.6.0&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2375" target="_blank" rel="noopener noreferrer">PT-2375&lt;/a>: When pt-table-sync is used on a table with a GENERATED AS column, it fails because we cannot REPLACE/INSERT values into a GENERATED column.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">`requestStatus` tinyint(1) GENERATED ALWAYS AS (if((`provRequired` = 0),(`httpSyncStatus` not between 200 and 299),(`httpAsyncStatus` not between 200 and 299))) VIRTUAL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 3105 (HY000): The value specified for generated column 'requestStatus' in table 'qqq' is not allowed.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The —-ignore-columns parameter specifically states that if a REPLACE/INSERT is needed, all columns will be used. Due to this, the pt-table-sync does not work with the generated columns.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.6.0&lt;/p>
&lt;h2 id="pmm-percona-monitoring-and-management">PMM [Percona Monitoring and Management]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12013" target="_blank" rel="noopener noreferrer">PMM-12013:&lt;/a> If we add many RDS instances to the PMM server, say 200+, and Change the prom scrape.maxScrapeSize to the value that allows the VM to parse the reply from the exporter, then the metrics are gathered unreliably, there are gaps, and the exporter`s RSS feed goes to like 5GB for instance. This concludes that rds_exporter is unreliable for large deployments.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.35.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PMM 3.0.0-Beta available as Tech Preview]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12161" target="_blank" rel="noopener noreferrer">PMM-12161:&lt;/a> In the Mongodb cluster summary page, Under QPS of Config Services dashboard, it is being clubbed configRS, mongoS and mongod servers. This results in too many configuration services under the QPS of the Config Services dashboard.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.42.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PMM 3.1 [Yet to Release]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12993" target="_blank" rel="noopener noreferrer">PMM-12993:&lt;/a> In PMM, CPU metrics have a label “mode” to identify between CPU info: sys, iowait, nice, user, idle, etc. With 1 rds instance, the metric is perfectly fine. However, after adding more instances, the CPU metric is still collected, but the “mode” label is empty, which breaks the graphs in the Advanced Data Exploration dashboard.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.41.1, 2.41.2&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13148" target="_blank" rel="noopener noreferrer">PMM-13148&lt;/a>: If we run the queries without using the schema name, then we don`t see such queries in the QAN.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql [localhost:8036] {msandbox} ((none)) > update test.joinit set g=100,t="06:44:50" where i=1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 1 row affected (0.00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rows matched: 1 Changed: 1 Warnings: 0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here, we can see we did not explicitly select the database name using the USE &lt;database> command and executed the query directly. This results in QAN not being able to capture such queries for analytics.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.41.2&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Run queries with USE &lt;dbname>; &lt;Query>;&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13252" target="_blank" rel="noopener noreferrer">PMM-13252:&lt;/a> A 500 error message is returned while creating the role with the existing name.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Enable Access roles in PMM Settings&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Open the Access role page and create a role with the name “Test. “&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Try to create a new role with the name “Test. “&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>It returns with Internal server error 500:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">in logs: msg="RPC /accesscontrol.v1beta1.AccessControlService/CreateRole done in 1.409839ms with unexpected error: pq: duplicate key value violates unique constraint "roles_title_key""&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.42.0&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> It is expected to be fixed in PMM 3&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13277" target="_blank" rel="noopener noreferrer">PMM-13277&lt;/a>: When we try to launch PMM using AWS AMI as mentioned in our docs. However, the AWS webpage works fine, and it logins, but every graph and details are blank with “Server error 502” The same can be seen in the log for Victoria metrics:&lt;/p>
&lt;p>The following error will be seen:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">2024-07-27T06:40:22.848Z panic /home/builder/rpm/BUILD/VictoriaMetrics-pmm-6401-v1.93.4/lib/mergeset/part_header.go:88 FATAL: cannot read "/srv/victoriametrics/data/indexdb/17D6772949F4A234/17D6772B9FDF298D/metadata.json": open /srv/victoriametrics/data/indexdb/17D6772949F4A234/17D6772B9FDF298D/metadata.json: no such file or directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">panic: FATAL: cannot read "/srv/victoriametrics/data/indexdb/17D6772949F4A234/17D6772B9FDF298D/metadata.json": open /srv/victoriametrics/data/indexdb/17D6772949F4A234/17D6772B9FDF298D/metadata.json: no such file or directory&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.42.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PMM 2.43.0&lt;/p>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3302" target="_blank" rel="noopener noreferrer">PXB-3302&lt;/a>: If the number of GTID sets is absolutely large on a MySQL instance, the output “GTID of the last change” in the Xtrabackup log is truncated compared to the full output in xtrabackup_binlog_info and xtrabackup_info. This can be an issue for external tools obtaining the GTID coordinates from the log as it would be impractical to get the coordinates from  xtrabackup_binlog_info or xtrabackup_info on a large, compressed xbstream file.&lt;/p>
&lt;p>Here is a snippet of a backup log:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">2024-06-03T10:18:59.678581+08:00 0 [Note] [MY-011825] [Xtrabackup] MySQL binlog position: filename 'mysql-bin.000002', position '10197', GTID of the last change ** REDACTED **,9f18624b-214f-11ef-871f-b445068273a0:1,9f1bf5ae-214f-11ef-871f-b445068273a0:1,9f1f6fac-214f-11ef-871f-b445068273a0:1,9f231076-214f-11ef-871f-b445068273a0:1,9f26d153-214f-11ef-871f-b445068273a0:1,9f2a5fdd-214f-11ef-871f-b445068273a0:1,9f2df6e8-214f-11ef-871f-b445068273a0:1,9f318143-214f-11ef-871f-b445068273a0:1,9f353351-214f-11ef-871f-b445068273a0:1,9f38f96c-214f-11ef-871f-b445068273a0:1,9f3cdc53-214f-11ef-871f-b445068273a0:1,9f40fcc0-214f-11ef-871f-b445068273a0:1,9f44955a-214f-11ef-871f-b445068273a0:1,9f481188-214f-11ef-871f-b445068&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Snippet of xtrabackup_binlog_info:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql-bin.000002 10197 ** REDACTED **,9fdd9048-214f-11ef-871f-b445068273a0:1,9fe138a3-214f-11ef-871f-b445068273a0:1,9fe4c3d8-214f-11ef-871f-b445068273a0:1,9fe82e39-214f-11ef-871f-b445068273a0:1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXB 8.4.0-1, 8.0.35-32&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3283" target="_blank" rel="noopener noreferrer">PXB-3283&lt;/a>: When xtrabackup takes a backup and exports a tablespace,  xtrabackup gets the wrong table definition from the ibd for tables that have changed the charset-collation in MySQL before backup.&lt;/p>
&lt;p>Eg:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE test.a (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a datetime DEFAULT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>the collation_id is 8 (latin1_swedish_ci)&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">shell> ibd2sdi /var/lib/mysql/test/a.ibd | jq '.[1].object.dd_object.columns[0]' | grep collation_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "collation_id": 8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When MySQL converts the charset on a table, it converts the date and time data types columns in the ibd file but not the data dictionary cache. The collation in the ibd does not match that of the data dictionary.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ALTER TABLE test.a CONVERT TO CHARACTER SET utf8mb4 collate utf8mb4_unicode_ci;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The collation_id becomes 224 (utf8mb4_unicode_ci)&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">shell> ibd2sdi /var/lib/mysql/test/a.ibd | jq '.[1].object.dd_object.columns[0]' | grep collation_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "collation_id": 224&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The collation_id of the copied table is 8 (latin1_swedish_ci)&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">create table xb.a like test.a;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shell> ibd2sdi /var/lib/mysql/xb/a.ibd | jq '.[1].object.dd_object.columns[0]' | grep collation_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "collation_id": 8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When xtrabackup exports the tablespace, the collation_id is 224 in ibd. Xtrabackup will write it to cfg metadata file.&lt;/p>
&lt;p>When MySQL imports a tablespace, MySQL gets an error Column %s precise type mismatch because the collation_id of MySQL does not match that of xtrabackup.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXB 8.4.0-1, 8.0.35-31&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-2797" target="_blank" rel="noopener noreferrer">PXB-2797&lt;/a>: When importing a single table (IMPORT TABLESPACE) from a backup made using xtrabackup and the table contains a full-text index, the import process will error out with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ERROR 1808 (HY000) at line 132: Schema mismatch (Index xxxxxx field xxxxxx is ascending which does not match metadata file which is descending)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.28-20&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXB 8.0.35-31&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3210" target="_blank" rel="noopener noreferrer">PXB-3210&lt;/a>: PXB fails to build on macOS since 8.0.33-28 due to FIND_PROCPS()&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CMake Error at cmake/procps.cmake:29 (MESSAGE):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cannot find proc/sysinfo.h or libproc2/meminfo.h in . You can pass it to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CMake with -DPROCPS_INCLUDE_PATH=&lt;path> or install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> procps-devel/procps-ng-devel/libproc2-dev package
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Call Stack (most recent call first):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage/innobase/xtrabackup/src/CMakeLists.txt:24 (FIND_PROCPS)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.33-28, 8.0.34-29, 8.0.35-30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXB 8.0.35-31&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3130" target="_blank" rel="noopener noreferrer">PXB-3130&lt;/a>: Performing upgrade from PS 8.0.30 -> PS 8.0.33 using PXB&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Use PXB 8.0.30 on PS 8.0.30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Copy to new host&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Prepare using PXB 8.0.33&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Start PS 8.0.33&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Which results in the Assertion:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">I0825 22:33:01.738917 05155 ???:1] xtrabackup80-apply-log(stderr) - InnoDB: Assertion failure: log0recv.cc:4353:log.m_files.find(recovered_lsn) != log.m_files.end()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXB 8.0.35-31&lt;/p>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1398" target="_blank" rel="noopener noreferrer">K8SPXC-1398:&lt;/a> Scheduled PXC backup job pod fails to complete the process successfully in a random/sporadic fashion.&lt;/p>
&lt;p>Error Returns As:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+ EXID_CODE=4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ '[' -f /tmp/backup-is-completed ']'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ log ERROR 'Backup was finished unsuccessfull'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terminating processProcess completed with error: /usr/bin/run_backup.sh: 4 (Interrupted system call)2024-05-03 09:39:08 [ERROR] Backup was finished unsuccessfull
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ exit 4&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 1.13.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXCO 1.16.0 [Yet to Release]&lt;/p>
&lt;p>Note: Since we don`t have steps to reproduce the issue, it is hard to confirm whether the fix is working as expected. Please feel free to provide feedback or create a Jira if required.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1397" target="_blank" rel="noopener noreferrer">K8SPXC-1397:&lt;/a> The operator`s default configuration makes the cluster unusable if TDE (Transparent data encryption) is used; the entry point of the PXC container configures the parameter binlog_rotate_encryption_master_key_at_startup. As a workaround, binlog_rotate_encryption_master_key_at_startup should be disabled. However, it has security implications.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 1.12.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXCO 1.16.0 [Yet to Release]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-1222" target="_blank" rel="noopener noreferrer">K8SPXC-1222:&lt;/a> Upgrading Cluster Fails When Dataset Has Large Number Of Tables. When the operator replaces the first pod with one with the new version, it fails to start up and gets stuck in a loop that restarts every 120 seconds.&lt;/p>
&lt;p>The problem looks like from pxc-entrypoint.sh:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">for i in {120..0}; do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if echo 'SELECT 1' | "${mysql[@]}" &amp;>/dev/null; then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> echo 'MySQL init process in progress...'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sleep 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> done&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 1.11.0, 1.12.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PXCO 1.16.0 [Yet to Release]&lt;/p>
&lt;h2 id="orchestrator">Orchestrator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/DISTMYSQL-406" target="_blank" rel="noopener noreferrer">DISTMYSQL-406&lt;/a>: Orchestrator 3.2.6-11 shows the MySQLOrchestratorPassword variable value in the error log and when accessing the web interface.&lt;/p>
&lt;p>E.g.:&lt;/p>
&lt;p>Create a MySQL and Orchestrator node&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql -e "CREATE USER 'orchestrator_srv'@'%' IDENTIFIED BY 'orc_server_password'; GRANT ALL ON orchestrator.* TO 'orchestrator_srv'@'%';"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Configure Orchestrator to use node0 as MySQL backend database&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-32">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">vi /usr/local/orchestrator/orchestrator.conf.json &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add the following lines and remove sqlite options:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-33">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> "MySQLOrchestratorHost": "node_0_IP",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "MySQLOrchestratorPort": 3306,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "MySQLOrchestratorDatabase": "orchestrator",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "MySQLOrchestratorUser": "orchestrator_srv",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "MySQLOrchestratorPassword": "orc_server_password",&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>On node1, there are several messages showing the backend password:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-34" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-34">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Feb 28 23:26:03 XX-XX-node1 orchestrator[4262]: 2024-02-28 23:26:03 ERROR 2024-02-28 23:26:03 ERROR QueryRowsMap(orchestrator_srv:orc_server_password@tcp(10.124.33.138:3306)/orchestrator?timeout=1s&amp;readTimeout=30s&amp;rejectReadOnly=false&amp;interpolateParams=true) select hostname, token, first_seen_active, last_seen_Active from active_node where anchor = 1: dial tcp 10.124.33.138:3306: connect: connection refused&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.36(PS)&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 8.4.0(PS)&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;p>&lt;a href="https://jira.percona.com" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p>
&lt;hr>
&lt;p>About Percona:&lt;/p>
&lt;p>As the only provider of distributions for all three of the most popular open source databases—PostgreSQL, MySQL, and MongoDB—Percona provides &lt;a href="https://www.percona.com/services/consulting" target="_blank" rel="noopener noreferrer">expertise&lt;/a>, &lt;a href="https://www.percona.com/software" target="_blank" rel="noopener noreferrer">software&lt;/a>, &lt;a href="https://www.percona.com/services/support/mysql-support" target="_blank" rel="noopener noreferrer">support&lt;/a>, and &lt;a href="https://www.percona.com/services/managed-services" target="_blank" rel="noopener noreferrer">services&lt;/a> no matter the technology.&lt;/p>
&lt;p>Whether its enabling developers or DBAs to realize value faster with tools, advice, and guidance, or making sure applications can scale and handle peak loads, Percona is here to help.&lt;/p>
&lt;p>Percona is committed to being open source and preventing vendor lock-in. Percona contributes all changes to the upstream community for possible inclusion in future product releases.&lt;/p></content:encoded><author>Aaditya Dubey</author><category>PMM</category><category>Kubernetes</category><category>MySQL</category><category>PostgreSQL</category><category>Percona</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2024/11/BugReportOctober2024_hu_236f422c0e93c589.jpg"/><media:content url="https://percona.community/blog/2024/11/BugReportOctober2024_hu_ee8f7570141a1ec5.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.43.0 Preview Release</title><link>https://percona.community/blog/2024/09/12/preview-release/</link><guid>https://percona.community/blog/2024/09/12/preview-release/</guid><pubDate>Thu, 12 Sep 2024 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.43.0 Tech Preview Release Hello everyone! Percona Monitoring and Management (PMM) 2.43.0 is now available as a Tech Preview Release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-2430-tech-preview-release">Percona Monitoring and Management 2.43.0 Tech Preview Release&lt;/h2>
&lt;p>Hello everyone! Percona Monitoring and Management (PMM) 2.43.0 is now available as a Tech Preview Release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>To see the full list of changes, check out the &lt;a href="https://pmm-doc-pr-1271.onrender.com/release-notes/2.43.0.html" target="_blank" rel="noopener noreferrer">PMM 2.43.0 Tech Preview Release Notes&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker-installation">PMM server Docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Run PMM Server with Docker instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.43.0-rc&lt;/code>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-29.tar.gz" target="_blank" rel="noopener noreferrer">Download AMD64&lt;/a> or &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client-arm/pmm2-client-latest-49.tar.gz" target="_blank" rel="noopener noreferrer">Download ARM64&lt;/a> pmm2-client tarball for 2.43.0.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To install pmm2-client package, enable testing repository via Percona-release:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;ol start="3">
&lt;li>Install pmm2-client package for your OS via Package Manager.&lt;/li>
&lt;/ol>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Run PMM Server as a VM instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.43.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.43.0.ova file&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Run PMM Server hosted at AWS Marketplace instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-0db618c7da6e202f4&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us on the [Percona Community Forums](&lt;a href="https://forums.percona.com/]" target="_blank" rel="noopener noreferrer">https://forums.percona.com/]&lt;/a>.&lt;/p></content:encoded><author>Ondrej Patocka</author><category>PMM</category><category>Release</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Timeline for Database on Kubernetes</title><link>https://percona.community/blog/2024/07/16/timeline-for-database-on-kubernetes/</link><guid>https://percona.community/blog/2024/07/16/timeline-for-database-on-kubernetes/</guid><pubDate>Tue, 16 Jul 2024 00:00:00 UTC</pubDate><description>The Evolution Since its inception in June 2014, Kubernetes has dramatically transformed container orchestration, revolutionizing the management and scaling of applications. To mark its tenth anniversary, the Data on Kubernetes Community (DoKC) released an infographic showcasing key milestones and community contributions to the evolution of operators for managing stateful applications. This project was made possible by the collaboration of DoKC members Edith Puclla, Sergey Pronin, Robert Hodges, Gabriele Bartolini, Chris Malarky, Mark Kember, Paul Au, and Luciano Stabel.</description><content:encoded>&lt;h2 id="the-evolution">The Evolution&lt;/h2>
&lt;p>Since its inception in June 2014, &lt;strong>Kubernetes&lt;/strong> has dramatically transformed container orchestration, revolutionizing the management and scaling of applications. To mark its tenth anniversary, the &lt;a href="https://dok.community/" target="_blank" rel="noopener noreferrer">Data on Kubernetes Community (DoKC)&lt;/a> released an infographic showcasing key milestones and community contributions to the evolution of operators for managing stateful applications. This project was made possible by the collaboration of DoKC members &lt;strong>Edith Puclla, Sergey Pronin, Robert Hodges, Gabriele Bartolini, Chris Malarky, Mark Kember, Paul Au, and Luciano Stabel&lt;/strong>.&lt;/p>
&lt;p>Explore the infographic to see how Kubernetes has shaped the future of database management on Kubernetes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1236" height="877" sizes="(max-width: 1236px) 100vw, 1236px" srcset="https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_ddf3c1d5bcd44803.webp 768w, https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_4a74a45a6b86d47d.webp 480w, https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_3307868874da4716.webp 1236w" src="https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_3307868874da4716.webp" alt="Databases Kubernetes Timeline" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="adoption-and-impact">Adoption and Impact&lt;/h2>
&lt;p>The &lt;a href="https://www.cncf.io/" target="_blank" rel="noopener noreferrer">CNCF&lt;/a> says that 84% of organizations are using or considering Kubernetes, with 70% running stateful applications on it in production. The number of users and containers has grown, showing that more people are contributing, adopting cloud-native technologies, and finding new ways to use Kubernetes to manage stateful applications.&lt;/p>
&lt;h2 id="looking-ahead">Looking Ahead&lt;/h2>
&lt;p>As we celebrate ten years of Kubernetes, the way databases are integrated keeps improving, thanks to community efforts and new technology. &lt;strong>Percona Everest is an excellent example of this progress&lt;/strong>. It’s more than just a tool for databases; it represents the future of running databases on Kubernetes. It’s open-source and makes running any database on cloud-based Kubernetes clusters easy. If you want to try it, visit our &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">Percona Everest GitHub Repository&lt;/a> and give us a star if you like it. For feedback or comments, join the &lt;a href="https://forums.percona.com/c/percona-everest/81" target="_blank" rel="noopener noreferrer">Percona Forum&lt;/a> for Percona Everest discussion.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="945" height="221" sizes="(max-width: 945px) 100vw, 945px" srcset="https://percona.community/blog/2024/07/percona-everest_hu_f902b0833e8e6e8b.webp 768w, https://percona.community/blog/2024/07/percona-everest_hu_38c31d90fe1661b4.webp 480w, https://percona.community/blog/2024/07/percona-everest_hu_8a2f5ada76a60f18.webp 945w" src="https://percona.community/blog/2024/07/percona-everest_hu_8a2f5ada76a60f18.webp" alt="Percona Everest" loading="lazy" />&lt;/figure>&lt;/p></content:encoded><author>Edith Puclla</author><category>CNCF</category><category>Percona Everest</category><category>Kubernetes</category><category>DoK</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_567dc635c8d5b739.jpg"/><media:content url="https://percona.community/blog/2024/07/databases-kubernetes-timeline_hu_1372998829c52fee.jpg" medium="image"/></item><item><title>Percona Joins Community Over Code 2024 in Bratislava, Slovakia</title><link>https://percona.community/blog/2024/06/21/percona-joins-community-over-code-2024-in-bratislava-slovakia/</link><guid>https://percona.community/blog/2024/06/21/percona-joins-community-over-code-2024-in-bratislava-slovakia/</guid><pubDate>Fri, 21 Jun 2024 00:00:00 UTC</pubDate><description>Last week, I participated as a speaker for the first time at Community Over Code 2024.</description><content:encoded>&lt;p>Last week, I participated as a speaker for the first time at Community Over Code 2024.&lt;/p>
&lt;h2 id="community-over-code">Community Over Code&lt;/h2>
&lt;p>&lt;a href="https://communityovercode.org/" target="_blank" rel="noopener noreferrer">Community Over Code&lt;/a> is a key principle at Apache, highlighting the importance of having a solid and collaborative community rather than just focusing on the code. While good code is essential, the community’s strength and resilience keep a project going and growing. I love how this is expressed in the “Apache Way.”&lt;/p>
&lt;p>Renaming ApacheCon to “Community Over Code” reflects this idea, emphasizing the central role of community in Apache’s approach. This year, &lt;a href="https://eu.communityovercode.org/" target="_blank" rel="noopener noreferrer">Community Over Code was in Bratislava, Slovakia.&lt;/a>
My talk was in the Community track, where I spoke about &lt;a href="https://www.outreachy.org/" target="_blank" rel="noopener noreferrer">Outreachy&lt;/a> Internship, &lt;a href="https://apache.org/" target="_blank" rel="noopener noreferrer">the Apache Software Foundation&lt;/a>, and &lt;a href="https://airflow.apache.org/" target="_blank" rel="noopener noreferrer">Apache Airflow&lt;/a>. My goal was to celebrate the success of the Outreachy program, which has surpassed 1,000 internships, and I am proud to be one of them.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1296" height="726" sizes="(max-width: 1296px) 100vw, 1296px" srcset="https://percona.community/blog/2024/06/coc-talk_hu_6e305270c3a2f7ef.webp 768w, https://percona.community/blog/2024/06/coc-talk_hu_16002d1245a38e4.webp 480w, https://percona.community/blog/2024/06/coc-talk_hu_3fdd63b06ebcfa9c.webp 1296w" src="https://percona.community/blog/2024/06/coc-talk_hu_3fdd63b06ebcfa9c.webp" alt="Community Over Code Talk" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I talked about the community, shared success stories, and provided clear examples, such as &lt;a href="https://www.linkedin.com/in/bowrna/" target="_blank" rel="noopener noreferrer">Bowrna Prabhakaran&lt;/a> and &lt;a href="https://www.linkedin.com/in/ephraimanierobi/" target="_blank" rel="noopener noreferrer">Ephraim Anierobi&lt;/a>, and shared my personal experience. I also explained how Outreachy, along with my mentors from Apache Airflow, &lt;a href="https://www.linkedin.com/in/jarekpotiuk/" target="_blank" rel="noopener noreferrer">Jarek Potiuk&lt;/a>, and &lt;a href="https://www.linkedin.com/in/elad-kalif-811b4887/" target="_blank" rel="noopener noreferrer">Elad Kalif&lt;/a>, boosted my professional career in Open Source 03 years ago.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1832" height="1038" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/coc-community_hu_98d5bcf736cd5321.webp 768w, https://percona.community/blog/2024/06/coc-community_hu_861c8eed8ea9381a.webp 480w, https://percona.community/blog/2024/06/coc-community_hu_16dc78f5b809b56c.webp 1400w" src="https://percona.community/blog/2024/06/coc-community_hu_16dc78f5b809b56c.webp" alt="Community Over Code Slide" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="working-at-percona">Working at Percona&lt;/h2>
&lt;p>Now I work for Percona, which has been recognized as one of &lt;a href="https://www.inc.com/profile/percona" target="_blank" rel="noopener noreferrer">Inc. Magazine’s 2024 Best Workplaces&lt;/a>! I love my job and the amazing things we are building at Percona.
&lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a> is an open-source software company that fully believes that open source should remain open throughout. This belief inspires me to work at Percona.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1308" height="956" sizes="(max-width: 1308px) 100vw, 1308px" srcset="https://percona.community/blog/2024/06/coc-percona_hu_1d46b4405fdfa201.webp 768w, https://percona.community/blog/2024/06/coc-percona_hu_d63c3a774bb84208.webp 480w, https://percona.community/blog/2024/06/coc-percona_hu_3bc481d45c13ccc4.webp 1308w" src="https://percona.community/blog/2024/06/coc-percona_hu_3bc481d45c13ccc4.webp" alt="Work At Percona" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="what-were-focused-on-now">What We’re Focused on Now&lt;/h2>
&lt;p>If you’re interested in improving database performance on Kubernetes, you will love &lt;a href="https://docs.percona.com/everest/index.html" target="_blank" rel="noopener noreferrer">Percona Everest&lt;/a>. It is our cloud-native solution for efficiently managing and running databases on Kubernetes. With a very user-friendly interface, you can run any database on any cloud provider or on-premises.&lt;/p>
&lt;p>Here are some use cases where you might use Percona Everest.&lt;/p>
&lt;ul>
&lt;li>Seeking ways to make the development of internal platforms easier and faster.&lt;/li>
&lt;li>Looking for affordable alternatives to public Database-as-a-Service (DBaaS) offerings.&lt;/li>
&lt;li>Building multi-cloud or hybrid cloud setups to meet data compliance needs for multi-regional businesses.&lt;/li>
&lt;li>Wishing to leverage Kubernetes for its scalability and stability to run databases efficiently.&lt;/li>
&lt;li>Transitioning from monolithic to microservices architecture to modernize legacy database infrastructure.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1834" height="950" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/coc-percona-everest_hu_7696bf5973e3b37.webp 768w, https://percona.community/blog/2024/06/coc-percona-everest_hu_bb80008cba394de6.webp 480w, https://percona.community/blog/2024/06/coc-percona-everest_hu_9f65e753e2eb4591.webp 1400w" src="https://percona.community/blog/2024/06/coc-percona-everest_hu_9f65e753e2eb4591.webp" alt="Percona Everest" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Any feedback is welcome on our &lt;a href="https://forums.percona.com/c/percona-everest/81" target="_blank" rel="noopener noreferrer">Percona Everest forum&lt;/a>, and if you like it, give us a start on GitHub: github.com/percona/everest.&lt;/p></content:encoded><author>Edith Puclla</author><category>Percona Everest</category><category>Kubernetes</category><category>Databases</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2024/06/coc-talk_hu_951765de4549236a.jpg"/><media:content url="https://percona.community/blog/2024/06/coc-talk_hu_4dc8c3844cc16894.jpg" medium="image"/></item><item><title>Let's take a look at Percona Everest 1.0.0 RC</title><link>https://percona.community/blog/2024/06/14/lets-take-a-look-at-percona-everest-1.0.0-rc/</link><guid>https://percona.community/blog/2024/06/14/lets-take-a-look-at-percona-everest-1.0.0-rc/</guid><pubDate>Fri, 14 Jun 2024 00:00:00 UTC</pubDate><description>Hi, the Percona Everest 1.0.0-rc1 release was published on GitHub.</description><content:encoded>&lt;p>Hi, the Percona Everest 1.0.0-rc1 release was published on &lt;a href="https://github.com/percona/everest/releases" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.&lt;/p>
&lt;p>&lt;a href="https://percona.community/projects/everest/">Percona Everest&lt;/a> is the first open source cloud-native platform for provisioning and managing PostgreSQL, MongoDB and MySQL database clusters.&lt;/p>
&lt;p>I want to tell you how to install it so you can try it out.&lt;/p>
&lt;p>&lt;strong>RC builds aren’t meant for the general public; we don’t support upgrading from RC to stable versions. This means that this is only for testing and familiarizing yourself with the features. RC builds are not stable and are often buggy. There will be no upgrade. :)&lt;/strong>&lt;/p>
&lt;p>To get started, you will need a Kubernetes cluster. Right now, &lt;a href="https://docs.percona.com/everest/index.html" target="_blank" rel="noopener noreferrer">Percona Everest&lt;/a> is in Beta. Don’t use production clusters; use test clusters in the cloud like GKE or local in Minikube, k3d, or Kind.&lt;/p>
&lt;p>I created a test cluster in GKE with the command:&lt;/p>
&lt;p>&lt;code>gcloud container clusters create test-everest-rc --project percona-product --zone us-central1-a --cluster-version 1.27 --machine-type n1-standard-4 --num-nodes=3&lt;/code>&lt;/p>
&lt;p>Delete it after the test with the command:&lt;/p>
&lt;p>&lt;code>gcloud container clusters delete test-everest-rc --zone us-central1-a&lt;/code>&lt;/p>
&lt;p>Now, we need &lt;a href="https://docs.percona.com/everest/install/installEverestCLI.html" target="_blank" rel="noopener noreferrer">Everest CLI&lt;/a> for the RC version; &lt;a href="https://github.com/percona/everest/releases" target="_blank" rel="noopener noreferrer">download&lt;/a> it from GitHub for your operating system.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2530" height="1464" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_80d755544b31e0e7.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_53a00daad64974a8.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_17ded22e6865119.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_17ded22e6865119.webp" alt="Percona Everest 1.0.0-RC1 GitHub" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I downloaded it, renamed it to &lt;code>everestctl&lt;/code>, and copied it to a folder for experimentation.&lt;/p>
&lt;p>Now, we need to make it executable&lt;/p>
&lt;p>&lt;code>chmod +x ./everestctl&lt;/code>&lt;/p>
&lt;p>Let’s check that everestctl works and that we have the correct version.&lt;/p>
&lt;p>&lt;code>./everestctl version&lt;/code>&lt;/p>
&lt;p>We can now install Percona Everest.&lt;/p>
&lt;p>&lt;code>./everestctl install --version-metadata-url https://check-dev.percona.com&lt;/code>&lt;/p>
&lt;p>Note that I use the &lt;code>--version-metadata-url parameter https://check-dev.percona.com&lt;/code>; this is required for RC builds.&lt;/p>
&lt;p>During the installation process, you must set one or more namespaces and databases.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1912" height="476" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_78b43f78437245c8.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_cb3652a265cc03d6.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_17afc40d48582176.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_17afc40d48582176.webp" alt="Percona Everest 1.0.0-RC1 Install" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Once the installation is complete, the new user authentication feature is the first significant change. You will be offered two commands.&lt;/p>
&lt;p>Command to retrieve the admin user password that was generated automatically during installation:&lt;/p>
&lt;p>&lt;code>./everestctl accounts initial-admin-password&lt;/code>&lt;/p>
&lt;p>Command to set a new password:&lt;/p>
&lt;p>&lt;code>./everestctl accounts set-password --username admin&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2006" height="708" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_dcf4a664c3018d29.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_36e0f14551e167e.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_4650bae37ad438b3.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_4650bae37ad438b3.webp" alt="Percona Everest 1.0.0-RC1 Admin Password" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now that we know the admin user password, we can open Percona Everest in a browser. Run the following command to use kubectl port forwarding to connect to Percona Everest without exposing the service:&lt;/p>
&lt;p>&lt;code>kubectl port-forward svc/everest 8080:8080 -n everest-system&lt;/code>&lt;/p>
&lt;p>More information in &lt;a href="https://docs.percona.com/everest/install/installEverest.html" target="_blank" rel="noopener noreferrer">the documentation&lt;/a>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1793" height="852" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_d78f62e48f564124.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_5b35f4aa98b45fd7.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_515de524536bd94f.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_515de524536bd94f.webp" alt="Percona Everest 1.0.0-RC1 Port Forward" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now you can open localhost:8080 in your browser and use admin and password to log in.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2918" height="1772" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_45170df1edadc86d.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_a2a591c994dfd7fb.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_955e080e014dc0f7.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_955e080e014dc0f7.webp" alt="Percona Everest 1.0.0-RC1 User Authentication" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Create a PostgreSQL cluster to test how it works.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2906" height="1768" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_f132f1eb05d46ca4.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_a9a3aadbd3c7c929.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_c1f169343dfc5247.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_c1f169343dfc5247.webp" alt="Percona Everest 1.0.0-RC1 Create PostgreSQL" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1786" height="1132" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_cc0f5af33e1bdf40.webp 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_9b5d97d026bad75d.webp 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_f7104e05ad4535eb.webp 1400w" src="https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_f7104e05ad4535eb.webp" alt="Percona Everest 1.0.0-RC1 PostgreSQL" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You can also create other databases, set up backups, and monitoring with &lt;a href="https://www.percona.com/open-source-database-monitoring-tools-for-mysql-mongodb-postgresql-more-percona" target="_blank" rel="noopener noreferrer">PMM&lt;/a>. By the way, PMM has some cool &lt;a href="https://www.percona.com/blog/postgresql-monitoring-with-percona-monitoring-and-management-a-redefined-summary/" target="_blank" rel="noopener noreferrer">new dashboards&lt;/a> in the Experimental section.&lt;/p>
&lt;p>Your feedback would be greatly appreciated. Create a new topic on &lt;a href="https://forums.percona.com/c/percona-everest/81" target="_blank" rel="noopener noreferrer">the forum&lt;/a> or issue on &lt;a href="https://github.com/percona/everest/issues" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.&lt;/p>
&lt;p>&lt;em>Don’t forget to delete the test cluster to save your budget.&lt;/em>&lt;/p>
&lt;p>Thank you very much.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>Percona Everest</category><category>Opensource</category><category>Kubernetes</category><category>MySQL</category><category>PostgreSQL</category><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2024/06/percona-everest-1-rc-cover_hu_269c13c75db4af18.jpg"/><media:content url="https://percona.community/blog/2024/06/percona-everest-1-rc-cover_hu_26541ea0da955d49.jpg" medium="image"/></item><item><title>Take a Clone it will last longer</title><link>https://percona.community/blog/2024/06/02/take-a-clone-it-will-last-longer/</link><guid>https://percona.community/blog/2024/06/02/take-a-clone-it-will-last-longer/</guid><pubDate>Sun, 02 Jun 2024 00:00:00 UTC</pubDate><description>So cloning is a great subject. I mean we clone sheep, we can clone human organs in time we might be able to clone humans, but thats a topic for scientist and philosophers.</description><content:encoded>&lt;p>So cloning is a great subject. I mean we clone sheep, we can clone human organs in time we might be able to clone humans,
but thats a topic for scientist and philosophers.&lt;/p>
&lt;p>&lt;strong>What is MySQL Replicaion:&lt;/strong>&lt;/p>
&lt;p>The MySQL clone plugin can be used to replicate data from a MySQL server to another MySQL server, and it supports replication. The cloning process creates a physical snapshot of the data, including tables, schemas, tablespaces, and data dictionary metadata. It tracks replication coordinates from the source server and transfers them to the replica, which allows replication to begin at a consistent position in the replication stream. This data includes the binary log position (filename, offset) and the gtid_executed GTID set. The replication metadata repositories are also copied during the cloning operation.&lt;/p>
&lt;p>The clone plugin that was released with MySQL Version 8.0.17. Its quick and very easy to setup. You can use it for so many different solutions. I’ve listed some common ones below, but I know that there are many more use cases.&lt;/p>
&lt;ol>
&lt;li>Create a new replica.&lt;/li>
&lt;li>Recover a replica which is out of sync with the primary.&lt;/li>
&lt;li>Quickly deploy MySQL servers with data set already in place.&lt;/li>
&lt;/ol>
&lt;p>In this article I will cover the basics of setting up and running a clone from and existing MySQL server. I will be using MySQL version 8.0.36.&lt;/p>
&lt;h2 id="prepare-the-source-server">Prepare the Source Server&lt;/h2>
&lt;ol>
&lt;li>Install the clone plugin.&lt;/li>
&lt;li>Create a clone user.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Install Clone plugin&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source > INSTALL PLUGIN clone SONAME 'mysql_clone.so';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify the clone plugin was installed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source > show plugins;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1724" height="78" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/clone-plugin-img1_hu_57142dafb0639ce2.webp 768w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_3e5c87e0299a855a.webp 480w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_3c5c63442626f01.webp 1400w" src="https://percona.community/blog/2024/06/clone-plugin-img1_hu_3c5c63442626f01.webp" alt="clone image 1" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Create Clone User&lt;/strong>&lt;/p>
&lt;p>Now we need to create a user to run the clone process. I highly suggest you create a user for the cloning. Please don’t use an ID with full admin rights. Create a user with the least amount of privileges needed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source > CREATE USER 'clone_user'@'%' IDENTIFIED BY 'S3k3rtPassWd';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">source > GRANT BACKUP_ADMIN ON *.* TO 'clone_user'@'%';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify your new clone user.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">source > show grants for clone_user;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="900" height="258" sizes="(max-width: 900px) 100vw, 900px" srcset="https://percona.community/blog/2024/06/clone-plugin-img2_hu_db50ee81225114c0.webp 768w, https://percona.community/blog/2024/06/clone-plugin-img2_hu_ce6c3e440b64a3cb.webp 480w, https://percona.community/blog/2024/06/clone-plugin-img2_hu_a82c7e04bb4a3957.webp 900w" src="https://percona.community/blog/2024/06/clone-plugin-img2_hu_a82c7e04bb4a3957.webp" alt="clone image 2" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Preparation on the source server is complete. Lets move on to the clone.&lt;/p>
&lt;h2 id="prepare-the-clone-server">Prepare the Clone Server&lt;/h2>
&lt;ol>
&lt;li>We need to start by installing the clone plugin.&lt;/li>
&lt;li>Then we need to define the source that clone will be based on.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Install plugin&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">clone > INSTALL PLUGIN clone SONAME 'mysql_clone.so';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify the clone plugin was installed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">clone > show plugins;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1724" height="78" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/06/clone-plugin-img1_hu_57142dafb0639ce2.webp 768w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_3e5c87e0299a855a.webp 480w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_3c5c63442626f01.webp 1400w" src="https://percona.community/blog/2024/06/clone-plugin-img1_hu_3c5c63442626f01.webp" alt="clone image 1" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Define Source Server&lt;/strong>&lt;/p>
&lt;p>You can use the source host name or host IP address.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">clone > SET GLOBAL clone_valid_donor_list='SOURCE_HOSTNAME:3306';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="start-and-monitor-the-cloning-process">Start and monitor the cloning process&lt;/h2>
&lt;p>We are now ready to kick off the cloning.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">clone > CLONE INSTANCE FROM clone_user@SOURCE_HOSTNAME:3306 IDENTIFIED BY 'S3k3rtPassWd';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Cloning should start now. If you have any issues, check your log files and very the steps above. Now that the cloning is running we can monitor the cloning process using this command.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">clone > SELECT * FROM performance_schema.clone_progress\G&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see in the output below, that the cloning has completed DROP DATA and is now working on FILE COPY.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1136" height="868" sizes="(max-width: 1136px) 100vw, 1136px" srcset="https://percona.community/blog/2024/06/clone-plugin-img3_hu_e2904728685a0b03.webp 768w, https://percona.community/blog/2024/06/clone-plugin-img3_hu_f7b64ccfcd2a448.webp 480w, https://percona.community/blog/2024/06/clone-plugin-img3_hu_88ceb9c86196f264.webp 1136w" src="https://percona.community/blog/2024/06/clone-plugin-img3_hu_88ceb9c86196f264.webp" alt="clone image 3" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="observations">Observations&lt;/h2>
&lt;p>Just recently I worked with a customer who moved a 5.6TB database from a Galera Cluster to standard MySQL replication. The data was moved from the source to replicas in two different locations. Timings are detailed below.&lt;/p>
&lt;ol>
&lt;li>Source to Replica in same location: 5.6TB in approxmently 50 mins.
&lt;ul>
&lt;li>112GB per minute.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Source to Replica in different geographic locations: 5.6TB in approxmently 1 hour and 45 mins.
&lt;ul>
&lt;li>53.33GB per minute.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>MySQL Clone Plugin’s robust performance and efficiency in managing large data transfers, making it an excellent tool for environments requiring quick and reliable data replication.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>The MySQL Clone Plugin offers several benefits, including:&lt;/p>
&lt;ol>
&lt;li>Fast Data Copying:
&lt;ul>
&lt;li>Enables rapid cloning of MySQL instances, facilitating quick data replication and environment setup.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Consistent Data State:
&lt;ul>
&lt;li>Ensures data consistency during cloning, avoiding issues that can arise from manual copying or inconsistent states.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Reduced Downtime:
&lt;ul>
&lt;li>Minimizes downtime during cloning operations, crucial for maintaining service availability.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ease of Use:
&lt;ul>
&lt;li>Simplifies the cloning process through straightforward commands, reducing the complexity for database administrators.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Automated Cloning Process:
&lt;ul>
&lt;li>Automates many steps involved in the cloning process, reducing the potential for human error and increasing efficiency.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="reference">Reference&lt;/h2>
&lt;p>&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/clone-plugin.html" target="_blank" rel="noopener noreferrer">The Clone Plugin&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.freepik.com/free-photo/dna-microscopic-view_854596.htm#fromView=search&amp;page=1&amp;position=1&amp;uuid=b58a4350-e1ba-44f8-9c0a-0c4498e84ac5" target="_blank" rel="noopener noreferrer">Image by kjpargeter on Freepik&lt;/a>&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Opensource</category><category>Replication</category><category>MySQL</category><category>Community</category><media:thumbnail url="https://percona.community/blog/2024/06/dna-microscopic-view_hu_67a13e60dded1a02.jpg"/><media:content url="https://percona.community/blog/2024/06/dna-microscopic-view_hu_fe8dd06a71438439.jpg" medium="image"/></item><item><title>Learn PostgreSQL and SQL Quickly and Free</title><link>https://percona.community/blog/2024/05/20/learn-postgresql-and-sql-quickly-and-free/</link><guid>https://percona.community/blog/2024/05/20/learn-postgresql-and-sql-quickly-and-free/</guid><pubDate>Mon, 20 May 2024 00:00:00 UTC</pubDate><description>Learn PostgreSQL and SQL Quickly and Free</description><content:encoded>&lt;p>Learn PostgreSQL and SQL Quickly and Free&lt;/p>
&lt;p>Want to learn PostgreSQL? What if you could learn PostgreSQL without having to install the database, load data, find sample data, and it was free? PGExamples.com is what you are looking for.&lt;/p>
&lt;p>First, let me state that I have no connection to the website or its author. I discovered PgExercises.com by accident and was impressed by its quality and completeness. I am so impressed that I am exploring a video series on it.&lt;/p>
&lt;h2 id="learn-sql-learn-postgresql">Learn SQL. Learn PostgreSQL.&lt;/h2>
&lt;p>&lt;a href="https://pgexercises.com/" target="_blank" rel="noopener noreferrer">PGexercises.com&lt;/a> is based on increasingly complex exercises based on a provided dataset. As you progress, the questions get trickier. You do not have to download and load the available data into a server to do the exercises.&lt;/p>
&lt;p>There are seven groups of exercises - Basic, Joins and Subqueries, Modifying Data, Aggregates, Date, String, and Recursive. The author recommends Learning SQL by Alan Beaulieu as a reference text. Any essential book, Structured Query Language, will serve you well.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="738" height="556" sizes="(max-width: 738px) 100vw, 738px" srcset="https://percona.community/blog/2024/05/image2_hu_9322dc84b19eceb.webp 480w, https://percona.community/blog/2024/05/image2_hu_ce5ac2a3b8939822.webp 738w" src="https://percona.community/blog/2024/05/image2_hu_ce5ac2a3b8939822.webp" alt="Learn SQL. Learn PostgreSQL." loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="first-exercise">First Exercise&lt;/h2>
&lt;p>The first exercise asks you to retrieve all the data from a specific table.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1008" height="986" sizes="(max-width: 1008px) 100vw, 1008px" srcset="https://percona.community/blog/2024/05/image4_hu_1a92c755e515ed56.webp 768w, https://percona.community/blog/2024/05/image4_hu_b65acc30de602a4a.webp 480w, https://percona.community/blog/2024/05/image4_hu_59f5ab03d3babf47.webp 1008w" src="https://percona.community/blog/2024/05/image4_hu_59f5ab03d3babf47.webp" alt="Learn SQL. First Exercise 1" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>At the top of the screen is an entity relationship map of the tables in the database. The table requested in the exercise is named cd.facilities.&lt;/p>
&lt;p>If you use the hint button (next to the Your Answer line) you will see the following:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="546" height="356" sizes="(max-width: 546px) 100vw, 546px" srcset="https://percona.community/blog/2024/05/image3_hu_94d0c5944a04f172.webp 480w, https://percona.community/blog/2024/05/image3_hu_85263259427b3039.webp 546w" src="https://percona.community/blog/2024/05/image3_hu_85263259427b3039.webp" alt="Learn SQL. First Exercise 2" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This should be enough of a clue to allow you, with the aid of your SQL reference, to find the answer. Enter your query and click on the Eun Query button.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="477" height="435" sizes="(max-width: 477px) 100vw, 477px" srcset="https://percona.community/blog/2024/05/image1_hu_a1175acfb5e2047d.webp 477w" src="https://percona.community/blog/2024/05/image1_hu_a1175acfb5e2047d.webp" alt="Learn SQL. First Exercise 3" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The green check means that the answer is correct. But what if you did not get it correct. Scroll down and select the Answers and Discussion button.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="686" height="346" sizes="(max-width: 686px) 100vw, 686px" srcset="https://percona.community/blog/2024/05/image5_hu_6b7291dffe0e40e4.webp 480w, https://percona.community/blog/2024/05/image5_hu_2667cc7fc396f7c4.webp 686w" src="https://percona.community/blog/2024/05/image5_hu_2667cc7fc396f7c4.webp" alt="Learn SQL. First Exercise 4" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Not only do you see the answer to the question, but there is a detailed analysis of the answer.&lt;/p>
&lt;h2 id="video-series">Video Series?&lt;/h2>
&lt;p>I am so impressed wth these exercises that the Percona Community Team is in discussion of creating a series of videos on them. Not only that, on of my colleagues who is learning PostgreSQL is volunteering to work through the exercises so that the series will feel like a study group. And I will comment on the query, possible alternatives when they exist, and help you work through the ‘rough spots’.&lt;/p>
&lt;p>If you want to work along, please do. Let us know where you struggle or the places we are redundant. Out goal is to provide a first class learning experience to help people learn SQL and PostgreSQL.&lt;/p></content:encoded><author>David Stokes</author><author>Jan Wieremjewicz</author><category>Percona</category><category>Opensource</category><category>PostgreSQL</category><category>SQL</category><category>pg_jan</category><media:thumbnail url="https://percona.community/blog/2024/05/Cover-PG_hu_d7c0f71b592bb5ac.jpg"/><media:content url="https://percona.community/blog/2024/05/Cover-PG_hu_f7ee348d1693d499.jpg" medium="image"/></item><item><title>Release Roundup May 15, 2024</title><link>https://percona.community/blog/2024/05/15/release-roundup-may-15-2024/</link><guid>https://percona.community/blog/2024/05/15/release-roundup-may-15-2024/</guid><pubDate>Wed, 15 May 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates April 30 - May 15, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates April 30 - May 15, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes releases and updates that have been released since April 29, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-6015">Percona Distribution for MongoDB 6.0.15&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/6.0/release-notes-v6.0.15.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 6.0.15&lt;/a> was released on April 30, 2024. It is a freely available MongoDB database alternative that gives you a single solution that combines enterprise components from the open source community, designed and tested to work together. Please see the release notes for a full list of improvements and bug fixes.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 6.0.15&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-6015-12">Percona Server for MongoDB 6.0.15-12&lt;/h2>
&lt;p>On April 30, 2024, we released &lt;a href="https://docs.percona.com/percona-server-for-mongodb/6.0/release_notes/6.0.15-12.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 6.0.15-12&lt;/a>. It is an enhanced, source-available, and highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB Community Edition 6.0.15. It is based on MongoDB 6.0.15 Community Edition and supports the upstream protocols and drivers. Please see the release notes for a full list of improvements and bug fixes.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 6.0.15-12&lt;/a>&lt;/p>
&lt;h2 id="percona-xtradb-cluster-5744-31652">Percona XtraDB Cluster 5.7.44-31.65.2&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-xtradb-cluster/5.7/release-notes/5.7.44-31.65.2.html" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster 5.7.44-31.65.2&lt;/a> was released on May 2, 2024. This release is part of MySQL 5.7 Post-EOL Support from Percona, and the fixes are available to &lt;a href="https://www.percona.com/post-mysql-5-7-eol-support" target="_blank" rel="noopener noreferrer">MySQL 5.7 Post-EOL Support from Percona customers&lt;/a>.&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Opensource</category><category>MongoDB</category><category>MySQL</category><category>XtraDB</category><category>Releases</category><category>Percona</category><media:thumbnail url="https://percona.community/blog/2024/05/Roundup-May-15_hu_17a4b7960167bb8d.jpg"/><media:content url="https://percona.community/blog/2024/05/Roundup-May-15_hu_238202edaac71d25.jpg" medium="image"/></item><item><title>How to Provision a MongoDB Cluster in Kubernetes with Percona Everest Summary</title><link>https://percona.community/blog/2024/05/02/how-to-provision-a-mongodb-cluster-in-kubernetes-with-percona-everest-summary/</link><guid>https://percona.community/blog/2024/05/02/how-to-provision-a-mongodb-cluster-in-kubernetes-with-percona-everest-summary/</guid><pubDate>Thu, 02 May 2024 00:00:00 UTC</pubDate><description>Kubernetes continues evolving, and the complexity of deploying and managing databases within the ecosystem is a topic of considerable discussion and importance these days. This article summarizes a detailed discussion between Piotr Szczepaniak and Diogo Recharte, who offer insights and live demonstrations to simplify database operations on Kubernetes with a new technology for cloud-native applications: Percona Everest. If you want to watch the full video, check out How to Provision a MongoDB Cluster in Kubernetes Webinar.</description><content:encoded>&lt;p>&lt;strong>Kubernetes&lt;/strong> continues evolving, and the complexity of deploying and managing databases within the ecosystem is a topic of considerable discussion and importance these days. This article summarizes a detailed discussion between &lt;a href="https://www.linkedin.com/in/petersgd/" target="_blank" rel="noopener noreferrer">Piotr Szczepaniak&lt;/a> and &lt;a href="https://www.linkedin.com/in/diogo-recharte/" target="_blank" rel="noopener noreferrer">Diogo Recharte&lt;/a>, who offer insights and live demonstrations to simplify database operations on Kubernetes with a new technology for cloud-native applications: Percona Everest. If you want to watch the full video, check out &lt;a href="https://www.youtube.com/live/ITeM7Pdp4oc?si=XAeL_4myDdhyq38h" target="_blank" rel="noopener noreferrer">How to Provision a MongoDB Cluster in Kubernetes Webinar&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="664" height="381" sizes="(max-width: 664px) 100vw, 664px" srcset="https://percona.community/blog/2024/05/peterdiogo_hu_4f7ed81bafdbb693.webp 480w, https://percona.community/blog/2024/05/peterdiogo_hu_9bb10f7d54e56764.webp 664w" src="https://percona.community/blog/2024/05/peterdiogo_hu_9bb10f7d54e56764.webp" alt="Percona Everest Webinar" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Peter mentions that Initially, people were doubtful about using virtual machines for databases, just like they were skeptical about Kubernetes. However, the topic brings together many people who run databases on containers to share their use cases and new discussions at events like Data on &lt;a href="https://www.youtube.com/playlist?list=PLHgdNuGxrJt1eqQeSHJ4J-RydHO6-LTeW" target="_blank" rel="noopener noreferrer">Kubernetes Day at Kubecon&lt;/a>.&lt;/p>
&lt;p>The introduction of &lt;strong>StatefulSets&lt;/strong> and &lt;strong>Persistent Volumes&lt;/strong> has altered the perception of Kubernetes from being purely ephemeral to being capable of handling persistent data. This change is important for database applications that require data retention over time.&lt;/p>
&lt;p>The Kubernetes ecosystem is rapidly expanding. This growth is thanks to its open-source nature and the continuous addition of new functionalities, such as support for specialized hardware like GPUs, which are crucial for AI and machine learning applications.&lt;/p>
&lt;p>Peter also mentioned that its complexity is the main barrier to Kubernetes adoption for databases. Organizations often need help with the layer added by Kubernetes on top of database management. Also, failure in initial attempts to integrate Kubernetes can discourage organizations from further attempts, primarily due to a lack of internal expertise.&lt;/p>
&lt;h4 id="benefits-of-database-as-a-service-dbaas">Benefits of Database as a Service (DBaaS)&lt;/h4>
&lt;p>DBaaS significantly reduces the time required for database provisioning, which is particularly useful in organizations needing rapid deployment. Public and private DBaaS solutions offer scalability, which is crucial for handling varying workloads and organizational growth without compromising performance.&lt;/p>
&lt;h4 id="private-vs-public-dbaas">Private vs. Public DBaaS&lt;/h4>
&lt;p>Private DBaaS offers more extensive customization options and control over databases, which is essential for companies with specific needs that public solutions cannot meet.&lt;/p>
&lt;p>Data security and compliance with regulations are more manageable in a private DBaaS because it operate within the company’s internal infrastructure.&lt;/p>
&lt;h4 id="demo-to-deploying-mongodb-on-kubernetes">Demo  to Deploying MongoDB on Kubernetes&lt;/h4>
&lt;p>Diogo presented a demo of deploying a MongoDB database using Percona’s Everest platform on Kubernetes, where he showed how to handle daily operations and disaster recovery scenarios efficiently. Watch the &lt;a href="https://youtu.be/ITeM7Pdp4oc?t=1039" target="_blank" rel="noopener noreferrer">Percona Everest Demo on YouTube&lt;/a>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1247" height="517" sizes="(max-width: 1247px) 100vw, 1247px" srcset="https://percona.community/blog/2024/05/percona-everest-mongodb_hu_9a4d2b83ca0963dd.webp 768w, https://percona.community/blog/2024/05/percona-everest-mongodb_hu_c04594d1a2acda41.webp 480w, https://percona.community/blog/2024/05/percona-everest-mongodb_hu_b62b8c10e1fa2cd6.webp 1247w" src="https://percona.community/blog/2024/05/percona-everest-mongodb_hu_b62b8c10e1fa2cd6.webp" alt="Percona Everest Draw" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The session explained how Kubernetes operators and custom resources help manage databases more easily. They do this by simplifying complex processes and automating regular tasks.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1406" height="858" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/05/everest-gui_hu_2add29e930907f5f.webp 768w, https://percona.community/blog/2024/05/everest-gui_hu_640b7a4c58a7e8b0.webp 480w, https://percona.community/blog/2024/05/everest-gui_hu_bdec28dc7a95ff40.webp 1400w" src="https://percona.community/blog/2024/05/everest-gui_hu_bdec28dc7a95ff40.webp" alt="Percona Everest GUI" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Some questions that users asked in this presentation are:&lt;/p>
&lt;h3 id="how-do-we-handle-the-pv-when-the-pods-go-down">How do we handle the PV when the Pods go down?&lt;/h3>
&lt;p>The PV will remain in place; this is a standard functionality of a stateful set. After the Pod goes down, the replacement Pod will attach to the PVC, which is standard behavior for a stateful set.&lt;/p>
&lt;h3 id="what-happens-if-the-node-in-kubernetes-goes-down">What happens if the node in Kubernetes goes down?&lt;/h3>
&lt;p>It depends on the storage layer that you have configured in your cluster. If the storage class you are using is tied to that node, then placing it on a new node will provision a new one, and some reconciliation will occur within the database itself.&lt;/p>
&lt;h3 id="what-is-the-current-state-of-percona-everest">What is the current state of Percona Everest?&lt;/h3>
&lt;p>Percona Everest is currently in a Beta stage, and Percona aims to release a GA version. The project is fully open source, and anyone can join our project on GitHub. We appreciate feedback from the community.&lt;/p>
&lt;p>Do you want to send us feedback or contribute in this cool project? We are completely open-source, you can visit &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">Percona Everest on GitHub&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>Percona Everest</category><category>MongoDB</category><category>Kubernetes</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2024/05/percona-everest-mongodb_hu_6b827eddfe206ace.jpg"/><media:content url="https://percona.community/blog/2024/05/percona-everest-mongodb_hu_4dc3ff5621cad4a0.jpg" medium="image"/></item><item><title>Using ProxySQL Query Mirroring to test query performance on a new cluster</title><link>https://percona.community/blog/2024/05/01/using-proxysql-query-mirroring-to-test-query-peromance-on-a-new-cluster/</link><guid>https://percona.community/blog/2024/05/01/using-proxysql-query-mirroring-to-test-query-peromance-on-a-new-cluster/</guid><pubDate>Wed, 01 May 2024 00:00:00 UTC</pubDate><description>ProxySQL is an SQL aware proxy, which gives DBA’s fine grained control over clients’ access to the MySQL cluster. A key part of our DBA team’s process in testing and preparing for major MySQL version upgrades is comparing query plans using ProxySQL query mirroring. This feature allows us to mirror queries to another cluster / host, by configuring query rules. What makes mirroring particularly useful is the ability to selectively mirror queries based on the query digest, or client user. Results from the queries that are mirrored are not returned to the client, and are sent to /dev/null.</description><content:encoded>&lt;p>ProxySQL is an SQL aware proxy, which gives DBA’s fine grained control over clients’ access to the MySQL cluster. A key part of our DBA team’s process in testing and preparing for major MySQL version upgrades is comparing query plans using &lt;a href="https://proxysql.com/documentation/mirroring/" target="_blank" rel="noopener noreferrer">ProxySQL query mirroring&lt;/a>. This feature allows us to mirror queries to another cluster / host, by configuring query rules. What makes mirroring particularly useful is the ability to selectively mirror queries based on the query digest, or client user. Results from the queries that are mirrored are not returned to the client, and are sent to /dev/null.&lt;/p>
&lt;p>Before configuring ProxySQL for Query Mirroring, ensure that the clients that you want to mirror the queries for, are able to connect to both the current, and the new cluster. You should also ensure that the ProxySQL monitor can connect to the new cluster, otherwise ProxySQL will mark the new hosts as offline, and the queries will not be mirrored there.&lt;/p>
&lt;h2 id="to-set-up-query-mirroring-in-proxysql">To set up query mirroring in ProxySQL:&lt;/h2>
&lt;p>In order to set up query mirroring, you need to add the new hosts into the &lt;code>mysql_server&lt;/code> table in ProxySQL. This is how the current &lt;code>mysql_servers&lt;/code> table looks, before we add the new host that we want to mirror the queries to:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> SELECT hostgroup_id, hostname FROM mysql_servers;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| hostgroup_id | hostname |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 10 | 10.12.0.123 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.12.0.123 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.16.0.456 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.16.0.789 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4 rows in set (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It is important to choose a &lt;code>hostgroup_id&lt;/code> that is not yet in use. You can double check the currently configured host groups in the mysql hostgroups table, as you do not want to inadvertently add the mirror hosts into the production traffic!&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> select * from mysql_replication_hostgroups;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------+------------------+------------+----------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| writer_hostgroup | reader_hostgroup | check_type | comment |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------+------------------+------------+----------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 10 | 20 | read_only | Async Cluster |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------+------------------+------------+----------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Please note, in our example, we are using async replication, so we check the &lt;code>mysql_replication_hostgroups&lt;/code> table, but the hostgroups table you need to check, depends on the cluster architecture you are using:&lt;/p>
&lt;ul>
&lt;li>async replica clusters check the &lt;code>mysql_replications_hostgroups&lt;/code> table.&lt;/li>
&lt;li>galera clusters check the &lt;code>mysql_galera_hostgroups&lt;/code> table&lt;/li>
&lt;li>group replication check the &lt;code>mysql_group_replication_hostgroups&lt;/code> table.&lt;/li>
&lt;/ul>
&lt;p>We are using hostgroup 10 for the writer hostgroup, and hostgroup 20 for the reader. For this example, we will choose 100 for the mirror &lt;code>hostgroup_id&lt;/code>. Once you have decided on an unused hostgroup ID, add the new clusters’ nodes to the &lt;code>mysql_servers&lt;/code> table in ProxySQL.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> INSERT INTO mysql_servers(host, hostgroup, comment) VALUES ("10.12.0.987", 100, "mirror_cluster");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LOAD MYSQL SERVERS TO RUN;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SAVE MYSQL SERVERS TO DISK;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>mysql_servers&lt;/code> table will now include the new host:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> SELECT hostgroup_id, hostname FROM mysql_servers;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| hostgroup_id | hostname |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 10 | 10.12.0.123 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.12.0.123 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.16.0.456 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 20 | 10.16.0.789 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 100 | 10.12.0.987 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------+--------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4 rows in set (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In order to enable query mirroring, you need to update the &lt;code>mirror_hostgroup&lt;/code> column in the &lt;code>mysql_query_rules&lt;/code> table. When mirroring is not enabled, the value of the &lt;code>mirror_hostgroup&lt;/code> column is &lt;code>NULL&lt;/code>.
Our query rules before enabling query mirroring are defined as:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> select rule_id, username, match_digest, destination_hostgroup, mirror_hostgroup from mysql_query_rules;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+------------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| rule_id | username | match_digest | destination_hostgroup | mirror_hostgroup |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+------------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | myApplicationUser | ^SELECT.*FOR UPDATE | 10 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 2 | myApplicationUser | ^SELECT | 20 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+------------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To enable mirroring, we just need to update the &lt;code>mirror_hostgroup&lt;/code>. For this example, we will mirror all the &lt;code>SELECT&lt;/code> queries made by &lt;code>myApplicationUser&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">UPDATE mysql_query_rules SET mirror_hostgroup = 100 where rule_id=2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LOAD mysql query rules TO RUN;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SAVE mysql query rules TO DISK;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The rules should now be updated:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> select rule_id, username, match_digest, destination_hostgroup, mirror_hostgroup from mysql_query_rules;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+-----------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| rule_id | username | match_digest | destination_hostgroup | mirror_hostgroup |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+-----------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | myApplicationUser | ^SELECT.*FOR UPDATE | 10 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 2 | myApplicationUser | ^SELECT | 20 | 100 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------+-----------------------+---------------------+-----------------------+------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The incoming queries that match the query rule, (in our example above, this is all queries as matching the regular expression ‘^SELECT’, for myApplicationUser, excluding queries matching ‘^SELECT.*FOR UPDATE’), will now be mirrored to the new cluster. You can verify this by checking the MySQL processlist on the new cluster.&lt;/p>
&lt;p>The &lt;code>stats_mysql_query_digest&lt;/code> table on ProxySQL holds statistics for the queries that are being processed by ProxySQL. To use the &lt;code>stats_mysql_query_digest&lt;/code> table, the global variables &lt;code>mysql-commands_stats&lt;/code> and &lt;code>mysql-query_digests&lt;/code> must be set to true, which is the default.&lt;/p>
&lt;p>Comparing query performance between two clusters&lt;/p>
&lt;p>Query the &lt;code>stats_mysql_query_digest&lt;/code> table to compare the performance per query between the current and the new cluster:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MySQL> select
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (b.count_star+a.count_star)/2 as count,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cast(round(((b.sum_time + 0.0)/(b.count_star + 0.0))/((a.sum_time + 0.0)/(a.count_star + 0.0)),2)*100 as int) as percent,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cast(round(((b.sum_time + 0.0)/(b.count_star + 0.0))/((a.sum_time + 0.0)/(a.count_star + 0.0)),2)*100 as int)*(b.count_star+a.count_star)/2 as load ,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> substr(a.digest_text,1,150)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">from
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> stats_mysql_query_digest a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">inner join
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> stats_mysql_query_digest b on
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a.digest = b.digest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">where
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a.hostgroup = 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> and b.hostgroup = 100
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">order by
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> percent ASC;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this example, the current production cluster has hostgroup 10, and the new mirror cluster was assigned hostgroup 100. The queries with a percentage above 100 are the queries that perform slower on the new cluster, and may be worth investigating, while the queries with a percentage below 100 are more performant on the new cluster. To investigate queries, you can compare the EXPLAIN plan of the query on the current and the new cluster. We use PMM Query Analytics to compare query analytics and the explain plan of the queries on the two separate clusters.&lt;/p>
&lt;p>It is worth noting, that you should allow enough time for the MySQL buffer pool to get filled, before checking the &lt;code>stats_mysql_query_digest&lt;/code> table. Otherwise, the query times on the new cluster can be skewed, as the active dataset may not yet be in memory (whereas on the current cluster it might be). Also, keep in mind that if you are mirroring only a subset of queries, the load on the new cluster will be different to the current cluster, and could affect the query performance on the new cluster, so that they appear significantly faster. Checking the execution plan of the query to see whether it has changed, is therefore more important than looking at overall load.&lt;/p>
&lt;p>To conclude, using query mirroring to test queries on a new system, before making the migration, allows you to compare latency and query plans per normalised query, and proactively detect any necessary alterations before switching live traffic to the new cluster.&lt;/p></content:encoded><author>Isobel Smith</author><category>ProxySQL</category><category>Upgrades</category><media:thumbnail url="https://percona.community/blog/2024/04/proxysql-query-mirroring_hu_ad2dbd1d1ccccd65.jpg"/><media:content url="https://percona.community/blog/2024/04/proxysql-query-mirroring_hu_b4ee1838b6b620a9.jpg" medium="image"/></item><item><title>Release Roundup April 30, 2024</title><link>https://percona.community/blog/2024/04/30/release-roundup-april-30-2024/</link><guid>https://percona.community/blog/2024/04/30/release-roundup-april-30-2024/</guid><pubDate>Tue, 30 Apr 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates April 17 - April 30, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates April 17 - April 30, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes releases and updates that have been released since April 15, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-distribution-for-mysql-830-1-ps-based-variant">Percona Distribution for MySQL 8.3.0-1 (PS-based variant)&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mysql/innovation-release/release-notes-ps-8.3.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MySQL 8.3.0-1 (PS-based variant)&lt;/a> was released on April 16, 2024. This release is based on Percona Server for MySQL 8.3.0-1 and merges the MySQL 8.3 code base. It introduces the following changes:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Percona updates the Binary Log UDFs to make them compatible with new tagged GTIDs (Global Transaction Identifiers).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9044" target="_blank" rel="noopener noreferrer">PS-9044&lt;/a>: Adds the following variables to MyRocks:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_block_cache_numshardbits" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_block_cache_numshardbits&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_check_iterate_bounds" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_check_iterate_bounds&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_compact_lzero_now" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_compact_lzero_now&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_file_checksums" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_file_checksums&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_max_file_opening_threads" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_max_file_opening_threads&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_partial_index_ignore_killed" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_partial_index_ignore_killed&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Changes the default values for the following variables:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_compaction_sequential_deletes" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_compaction_sequential_deletes&lt;/code>&lt;/a> from 0 to 14999&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_compaction_sequential_deletes_count_sd" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_compaction_sequential_deletes_count_sd&lt;/code>&lt;/a> from &lt;code>OFF&lt;/code> to &lt;code>ON&lt;/code>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_compaction_sequential_deletes_window" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_compaction_sequential_deletes_window&lt;/code>&lt;/a> from 0 to 15000&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_force_flush_memtable_now" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_force_flush_memtable_now&lt;/code>&lt;/a> from &lt;code>ON&lt;/code> to &lt;code>OFF&lt;/code>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-server/innovation-release/myrocks-server-variables.html#rocksdb_large_prefix" target="_blank" rel="noopener noreferrer">&lt;code>rocksdb_large_prefix&lt;/code>&lt;/a> from &lt;code>OFF&lt;/code> to &lt;code>ON&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mysql/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MySQL 8.3.0-1 (PS-based variant)&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mysql-83">Percona Server for MySQL 8.3&lt;/h2>
&lt;p>On April 16, 2024, we released &lt;a href="https://docs.percona.com/percona-server/innovation-release/release-notes/8.3.0-1.html" target="_blank" rel="noopener noreferrer">Percona Server for MySQL 8.3&lt;/a>. It includes all the features and bug fixes available in the MySQL 8.3 Community Edition in addition to enterprise-grade features developed by Percona. This release merges the MySQL 8.3 code base. Within this merge, Percona updates the Binary Log UDFs to make them compatible with new tagged GTIDs (Global Transaction Identifiers).&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-server-for-mysql" target="_blank" rel="noopener noreferrer">Download Percona Server for MySQL 8.3&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-708">Percona Distribution for MongoDB 7.0.8&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/7.0/release-notes-v7.0.8.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 7.0.8&lt;/a> was released on April 24, 2024. It is a freely available MongoDB database alternative that gives you a single solution that combines enterprise components from the open source community, designed and tested to work together. Bug fixes and improvements provided by MongoDB are included in Percona Distribution for MongoDB. Note: a number of issues with sharded multi-document transactions in sharded clusters of 2 or more shards have been identified that result in returning incorrect results and missing reads and writes. The issues occur when the transactions’ metadata is being concurrently modified by using the following operations: &lt;code>moveChunk&lt;/code>, &lt;code>moveRange&lt;/code>, &lt;code>movePrimary&lt;/code>, &lt;code>renameCollection&lt;/code>, &lt;code>drop&lt;/code>, and &lt;code>reshardCollection&lt;/code>. Please check the release notes for further information.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 7.0.8&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-708-5">Percona Server for MongoDB 7.0.8-5&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/7.0/release_notes/7.0.8-5.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 7.0.8-5&lt;/a> was released on April 24, 2024. It is an enhanced, source-available, and highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB Community Edition 7.0.8. A number of issues with sharded multi-document transactions in sharded clusters of 2 or more shards have been identified that result in returning incorrect results and missing reads and writes. The issues occur when the transactions’ metadata is being concurrently modified by using the following operations: &lt;code>moveChunk&lt;/code>, &lt;code>moveRange&lt;/code>, &lt;code>movePrimary&lt;/code>, &lt;code>renameCollection&lt;/code>, &lt;code>drop&lt;/code>, and &lt;code>reshardCollection&lt;/code>. Please check the release notes for further information.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 7.0.8-5&lt;/a>&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Percona</category><category>Opensource</category><category>MongoDB</category><category>MySQL</category><category>Releases</category><media:thumbnail url="https://percona.community/blog/2024/04/Roundup-April-30_hu_b1efd38c8e5e04b9.jpg"/><media:content url="https://percona.community/blog/2024/04/Roundup-April-30_hu_d80971074c5cdf0c.jpg" medium="image"/></item><item><title>Percona Bug Report: April 2024</title><link>https://percona.community/blog/2024/04/29/percona-bug-report-april-2024/</link><guid>https://percona.community/blog/2024/04/29/percona-bug-report-april-2024/</guid><pubDate>Mon, 29 Apr 2024 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://jira.percona.com/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs,&lt;/p>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9092" target="_blank" rel="noopener noreferrer">PS-9092&lt;/a>: A query over an InnoDB table that uses a backward scan over the index occasionally might return incorrect/incomplete results when changes to the table (for example, DELETEs in another or even the same connection followed by asynchronous purge) cause concurrent B-tree page merges.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 5.7.44, 8.0.35, 8.0.36&lt;/p>
&lt;p>&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=114248" target="_blank" rel="noopener noreferrer">114248&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Use descending indexes for the primary key. E.g.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE bugTest.testTable (key int unsigned, version bigint unsigned, rowmarker char(3) not null default 'aaa', value MEDIUMBLOB, PRIMARY KEY (key DESC, version DESC)) Engine=InnoDB;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9107" target="_blank" rel="noopener noreferrer">PS-9107&lt;/a>: A delete/insert into the secondary index is being change-buffered. This causes an insert into the ‘ibuf’ tree. There is a limit on the maximum size of the ibuf. So, on every ibuf insert, there is a compaction of ibuf tree (ibuf_contract). As part of ibuf_contract, we randomly open an ibuf page and apply the ibuf entries to the actual secondary index pages. After applying these ibuf entries from ibuf index, the ibuf tree goes on merging pages (optimistic vs pessimistic btree operations). To do this ibuf pessimistic delete on the tree, we save the cursor position, commit mtr and do a restore. This restore does a search and position again on the ibuf entry we were processing earlier, causing [MY-013183] [InnoDB] Assertion failure: ibuf0ibuf.cc:3833:ib::fatal triggered thread.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.34-26, 8.0.35-27, 8.0.36-28&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> PS 8.0.37-29 [Yet to Release]&lt;/p>
&lt;p>&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=114135" target="_blank" rel="noopener noreferrer">114135&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Disable &lt;a href="https://dev.mysql.com/doc/refman/8.3/en/innodb-parameters.html#sysvar_innodb_change_buffering" target="_blank" rel="noopener noreferrer">innodb_change_buffering&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9115" target="_blank" rel="noopener noreferrer">PS-9115&lt;/a>: MySQL crashes due to getting a native index from get_mutex_cond in group replication, and before the crash, the following set of warnings/Errors is generated:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[Warning] [MY-011630] [Repl] Plugin group_replication reported: 'Due to a plugin error, some transactions were unable to be certified and will now rollback.'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ERROR] [MY-011631] [Repl] Plugin group_replication reported: 'Error when trying to unblock non certified or consistent transactions. Check for consistency errors when restarting the service'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is in progress and expected to be included in an upcoming release of Percona Servers.&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> We can not guarantee that the bug will be avoided 100%, but shutting down/stopping group replication off-hours when the workload recedes should prevent the situation.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9121" target="_blank" rel="noopener noreferrer">PS-9121&lt;/a>: InnoDB updates the primary index but not the spatial index, which eventually corrupts the spatial index. The MySQL server crashes with “[ERROR] [MY-013183] [InnoDB] Assertion failure: row0ins.cc:268:!cursor->index->is_committed().” The update query changes the data from point(0.0000000000000099,0) to point(0.00000000000001, 0). When Innodb updates the record containing a spatial index, It updates the clustered index. This issue can be repeated using the following set of SQL statements.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE TABLE a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id INT PRIMARY KEY,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a GEOMETRY NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SPATIAL KEY (a)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ENGINE=InnoDB;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO a VALUES (1,POINT(0.0000000000000099, 0));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">UPDATE a SET a = Point(0.00000000000001, 0);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DELETE FROM a WHERE id = 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO a VALUES (1,POINT(0.0000000000000099, 0));&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.36-28, 8.X [Innovative Release]&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is in progress and expected to be included in an upcoming release of Percona Servers.&lt;/p>
&lt;p>&lt;strong>Upstream Bug:&lt;/strong> &lt;a href="https://bugs.mysql.com/bug.php?id=114252" target="_blank" rel="noopener noreferrer">114252&lt;/a>&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Please consider resetting the shape to a different value from the new values.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9109" target="_blank" rel="noopener noreferrer">PS-9109&lt;/a>: The Percona server’s slow query rate is not accurately logged, which is controlled via the &lt;a href="https://docs.percona.com/percona-server/8.0/slow-extended.html?h=log_slow_rate_type#log_slow_rate_type" target="_blank" rel="noopener noreferrer">Log_slow_rate_type&lt;/a> and &lt;a href="https://docs.percona.com/percona-server/8.0/slow-extended.html?h=log_slow_rate_type#log_slow_rate_limit" target="_blank" rel="noopener noreferrer">Log_slow_rate_limit&lt;/a> variables. Due to this issue, the slow query log records every query regardless of these variables’ values.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona Servers.&lt;/p>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4380" target="_blank" rel="noopener noreferrer">PXC-4380&lt;/a>: In a large cluster, for example, 15 nodes, in case one node has network issues and disconnects/re-connects/disconnects, the cluster might be sent to a non-primary state if the evs.install_timeout is reached.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 5.7.42-31-65, 8.0.33-25, 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> In such a big cluster, reaching consensus between nodes takes more time, so evs.install_timeout has to be adjusted. We can also configure the cluster to evict unresponsive nodes by setting evs.auto_evict=1. So, after investigation, we found that no fix is required, as everything works as expected/designed.&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Increasing the evs.install_timeout might fix the issue. The maximum value is 15S, which can still be reached depending on cluster size and how bad the flapping is.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">The default value for evs.install_timeout is evs.inactive_timeout/2.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The minimum value is evs.join_retrans_period
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The maximum time is evs.inactive_timeout + 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So, If we keep defaults:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">evs.join_retrans_period = 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">evs.inactive_timeout = 15s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">evs.install_timeout (default) = 7.5s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">evs.install_timeout(min) = 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">evs.install_timeout(max) = 16s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Please note that 15s is not a hard limit and is determined by evs.inactive_timeout&lt;/p>
&lt;p>We have set wsrep_provider_options=“evs.install_timeout=PT15S” for all nodes in the test environment and are now unable to reproduce the issue. So, we are on some timeout boundaries, and the above configuration parameters were introduced to fine-tune in such environments.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4367" target="_blank" rel="noopener noreferrer">PXC-4367&lt;/a>: Innodb semaphore wait timeout failure seen after upgrade from 8.0.34 to 8.0.35. This issue is a possible side effect of this &lt;a href="https://github.com/percona/percona-xtradb-cluster/pull/1854" target="_blank" rel="noopener noreferrer">patch&lt;/a>, Where PXC node acts as the async replica to some master and in parallel, the row is updated on PXC node and via replication, the PXC node hangs. To avoid the issue, upgrading to PXC 8.0.36 is recommended.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 8.0.36-28&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4363" target="_blank" rel="noopener noreferrer">PXC-4363&lt;/a>: Concurrent CREATE and DROP USER queries on different nodes lead to permanent lock.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">| 16 | system user | db1 | Query | 411 | Waiting for table metadata lock | drop user IF EXISTS `msandbox_rw11`@`localhost` | 411380 | 0 | &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>These queries cannot be cancelled or killed, and nodes refuse to be gracefully restarted. The shutdown is stuck with this forever. Only the forcible service kill helps restore the cluster.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">2024-01-18T17:49:24.470428Z 0 [Note] [MY-000000] [Galera] Closing slave action queue.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona Servers.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4399" target="_blank" rel="noopener noreferrer">PXC-4399&lt;/a>: FLUSH TABLES during writes to the table with unique keys stall the cluster node; due to the stall, it’s not possible to abort/kill any of the above connections. The node sends a permanent flow control pause. The only way to get out of this stall is to kill the node.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.33-25, 8.0.35-27&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona Servers.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4418" target="_blank" rel="noopener noreferrer">PXC-4418&lt;/a>: In MySQL, the optimizer creates a temp table definition with indexes, where 2 of them have the same name (&lt;auto_key2>, &lt;auto_key1>, &lt;auto_key2>). When the query is executed, a temp table is created, then MySql tries to access &lt;auto_key2>. In InnoDB, we search for index by name (dict_table_get_index_on_name()), which returns the wrong &lt;auto_key2>. Then row_sel_convert_mysql_key_to_innobase() crashes as structures are not aligned. Please note this bug affects only very complicated queries with many JOINs and subqueries. To repeat this bug, the internal temporary table needs to be created at least for two parts of the query.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.32-24, 8.0.34-26&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 8.0.36-28&lt;/p>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2190" target="_blank" rel="noopener noreferrer">PT-2190&lt;/a>:The &lt;a href="https://docs.percona.com/percona-toolkit/pt-show-grants.html" target="_blank" rel="noopener noreferrer">pt-show-grants&lt;/a> use SHOW CREATE USER command to obtain grants from the MySQL server. By default, this query returns values as they are stored in the mysql.user table. When using caching_sha256_password, such hash of the password could contain a special character. Therefore, it would not be possible to use output printed by pt-show-grants to re-create users in the database. Since version 3.6.0, pt-show-grants checks if it runs against MySQL version 8.0.17 or higher and sets session option &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_print_identified_with_as_hex" target="_blank" rel="noopener noreferrer">print_identified_as_hex&lt;/a> to true before running SHOW CREATE USER command. This allows to print commands that could be used to re-create users.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 3.6.0 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2215" target="_blank" rel="noopener noreferrer">PT-2215&lt;/a>: pt-table-sync does not recognize the privileges in roles for MariaDB&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.2&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona ToolKit.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2316" target="_blank" rel="noopener noreferrer">PT-2316&lt;/a>: pt-config-diff with –pid option is broken with “Can’t locate object method “make_PID_file” via package “Daemon” at /usr/bin/pt-config-diff line 5522” on Ubuntu 20.04&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.7&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona ToolKit.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2314" target="_blank" rel="noopener noreferrer">PT-2314&lt;/a>: pt-online-schema-change fails due to duplicate constraint names when it attempts to make a table copy for alteration.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.7&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona ToolKit.&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> Do not duplicate constraints name.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2322" target="_blank" rel="noopener noreferrer">PT-2322&lt;/a>: pt-mysql-summary does not detect jemalloc when installed as systemd.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 3.5.6, 3.5.7&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of Percona ToolKit.&lt;/p>
&lt;h2 id="pmm-percona-monitoring-and-management">PMM [Percona Monitoring and Management]&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-11583" target="_blank" rel="noopener noreferrer">PMM-11583&lt;/a>: On MySQL 8.0, &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_redo_log_capacity" target="_blank" rel="noopener noreferrer">innodb_redo_log_capacity&lt;/a> supersedes &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_files_in_group" target="_blank" rel="noopener noreferrer">innodb_log_files_in_group&lt;/a> and &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_file_size" target="_blank" rel="noopener noreferrer">innodb_log_file_size&lt;/a>, which eventually breaks InnoDB Logging graphs. Due to this issue, the user can not determine whether the combined InnoDB redo log file size has to be increased or not.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.41.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.41.3 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-13017" target="_blank" rel="noopener noreferrer">PMM-13017&lt;/a>: For certain db.collection.find(query, projection, options) queries, the Explain tab for QAN returns an error message saying, “error decoding key command: invalid JSON input; expected value for 64-bit integer.” Please note this issue specifically affects MongoDB monitoring.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.35.0, 2.37.1, 2.41.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in an upcoming release of PMM&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12522" target="_blank" rel="noopener noreferrer">PMM-12522&lt;/a>: When adding data relatively large chunks of data to MongoDB sharded cluster, pmm-agent log starts flooded with “level=error msg="cannot create metric for changelog… &amp; level=error msg="Failed to get database names:…” which eventually shows MongoS as disconnected in PMM UI (PMM-Inventory/Services).&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.39.0, 2.40.0, 2.41.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.41.3 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12880" target="_blank" rel="noopener noreferrer">PMM-12880&lt;/a>: pmm-admin &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/commands/pmm-admin.html#mongodb" target="_blank" rel="noopener noreferrer">–tls-skip-verify&lt;/a> does not work when &lt;a href="https://dev.mysql.com/doc/mysql-secure-deployment-guide/5.7/en/secure-deployment-user-accounts.html" target="_blank" rel="noopener noreferrer">x509 authentication&lt;/a> is used.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.41.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.41.3 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12989" target="_blank" rel="noopener noreferrer">PMM-12989&lt;/a>: PMM agent logs flooded with wrong log entries when monitoring auth-enabled arbiters. Please note this issue specifically affects MongoDB monitoring.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.41.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.41.3 [It is expected to be released soon]&lt;/p>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3251" target="_blank" rel="noopener noreferrer">PXB-3251&lt;/a>: When PXB fails to load the encryption key, the xtrabackup_logfile is still created in the target dir. This causes a second attempt at running PXB to fail with a new error. The &lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-files.html?h=xtrabackup_logfile" target="_blank" rel="noopener noreferrer">xtrabackup_logfile&lt;/a> file contains data needed to run the –prepare process. The bigger this file is, the longer the –prepare process will take to finish. So, PXB should not create any files on disk until the encryption key is loaded.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 8.0.35-30&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> The fix is expected to be included in future releases of PXB&lt;/p>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-496" target="_blank" rel="noopener noreferrer">K8SPG-496&lt;/a>: When a PostgreSQL Database is set to a paused state via spec, the operator waits until all backups for the Database finish. After the backups finish, the PostgreSQL Database shall be paused, which is not happening.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.3.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.3.1&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-494" target="_blank" rel="noopener noreferrer">K8SPG-494&lt;/a>: High vulnerabilities found for pgbackrest, Postgres &amp; pgbouncer package.&lt;/p>
&lt;p>For pgbackrest and postgres: &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-38408" target="_blank" rel="noopener noreferrer">CVE-2023-38408&lt;/a>&lt;/p>
&lt;p>For pgbouncer: &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-32067" target="_blank" rel="noopener noreferrer">CVE-2023-32067&lt;/a>&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.3.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.3.1&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-521" target="_blank" rel="noopener noreferrer">K8SPG-521&lt;/a>: The upgrade path described in the &lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/update.html#update-database-and-operator-version-2x" target="_blank" rel="noopener noreferrer">documentation&lt;/a> leads to disabled built-in extensions(pg_stat_monitor, pg_audit).&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.3.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.4.0 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-522" target="_blank" rel="noopener noreferrer">K8SPG-522&lt;/a>: Cluster is broken if PG_VERSION file is missing during the upgrade from 2.2.0 to 2.3.1.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.3.1&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.4.0 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> In order to fix the issue, please do the following:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Create PG_VERSION with contents 14 in DB instance pod.&lt;/p>
&lt;p>&lt;code>echo 14 > /pgdata/pg14/PG_VERSION&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Apply crd and rbac&lt;/p>
&lt;p>&lt;code>kubectl apply --force-conflicts --server-side -f crd.yaml&lt;/code>&lt;/p>
&lt;p>&lt;code>kubectl -n operatornew apply --force-conflicts --server-side -f rbac.yaml&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Restart the operator deployment.&lt;/p>
&lt;p>&lt;code>kubectl -n operatornew rollout restart deployment percona-postgresql-operator&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>At this moment, patronictl show-config starts to enlist extensions.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ kubectl -n operatornew exec cluster1-instance1-gsvs-0 -it -- bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Defaulted container "database" out of: database, replication-cert-copy, postgres-startup (init), nss-wrapper-init (init)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bash-4.4$ patronictl show-config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">loop_wait: 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">postgresql:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parameters:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> archive_command: pgbackrest --stanza=db archive-push "%p"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> archive_mode: 'on'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> archive_timeout: 60s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> huge_pages: 'off'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> jit: 'off'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password_encryption: scram-sha-256
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pg_stat_monitor.pgsm_query_max_len: '2048'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restore_command: pgbackrest --stanza=db archive-get %f "%p"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shared_preload_libraries: pg_stat_monitor,pgaudit&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Now extensions appear in the \dx output without pod restarts, but all Postgresql servers will be restarted:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-547" target="_blank" rel="noopener noreferrer">K8SPG-547&lt;/a>: The pgbackrest container can’t use pgbackrest 2.50. This is because pgbackrest 2.50 requires libssh2.so.1, which requires epel. Without that fix, microdnf installs pgbackrest 2.48, which creates inconsistency with the Postgresql container.&lt;/p>
&lt;p>&lt;strong>Reported Affected Version/s:&lt;/strong> 2.2.0&lt;/p>
&lt;p>&lt;strong>Fixed Version:&lt;/strong> 2.4.0 [It is expected to be released soon]&lt;/p>
&lt;p>&lt;strong>Workaround/Fix:&lt;/strong> This &lt;a href="https://github.com/percona/percona-docker/pull/960" target="_blank" rel="noopener noreferrer">patch&lt;/a> can be used until it is fixed.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;p>&lt;a href="https://jira.percona.com" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p></content:encoded><author>Aaditya Dubey</author><category>Percona</category><category>Opensource</category><category>PMM</category><category>Kubernetes</category><category>MySQL</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2024/04/BugReportApril2024_hu_de8550711f3fe3be.jpg"/><media:content url="https://percona.community/blog/2024/04/BugReportApril2024_hu_4e33d575799f088.jpg" medium="image"/></item><item><title>Deploying Percona Everest on GCP with Kubectl for Windows 11 Users</title><link>https://percona.community/blog/2024/04/19/deploying-percona-everest-on-gcp-with-kubectl-for-windows-11-users/</link><guid>https://percona.community/blog/2024/04/19/deploying-percona-everest-on-gcp-with-kubectl-for-windows-11-users/</guid><pubDate>Fri, 19 Apr 2024 00:00:00 UTC</pubDate><description>Welcome to this blog post! Today, our primary goal is to guide you through deploying Percona Everest on GCP using Kubectl, specifically for users on Windows 11. It’s been some time since I last used Windows, so this will be an excellent opportunity to do it from scratch.</description><content:encoded>&lt;p>Welcome to this blog post! Today, our primary goal is to guide you through deploying Percona Everest on GCP using Kubectl, specifically for users on Windows 11. It’s been some time since I last used Windows, so this will be an excellent opportunity to do it from scratch.&lt;/p>
&lt;p>Let me tell you a little bit about &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">Percona Everest&lt;/a>. You may have already heard it recently. It is the new open source tool launched by Percona and is already well-received by Kubernetes database users.&lt;/p>
&lt;p>&lt;strong>Percona Everest&lt;/strong> is an open source cloud-native database platform that helps developers deploy code faster, scale deployments rapidly, and reduce database administration overhead while regaining control over their data, database configuration, and DBaaS costs. It is designed for those who want to break free from vendor lock-in, ensure optimal database performance, enable cost-effective and right-sized database deployments, and reduce database administration overhead.&lt;/p>
&lt;p>If you use Windows and want to try the deployment and use of Percona Everest, you are in the right place.&lt;/p>
&lt;p>This image shows what Percona Everest does and what we want to achieve:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1223" height="625" sizes="(max-width: 1223px) 100vw, 1223px" srcset="https://percona.community/blog/2024/04/percona-everest_hu_b1b1eb6183991760.webp 768w, https://percona.community/blog/2024/04/percona-everest_hu_6ff28763b2511095.webp 480w, https://percona.community/blog/2024/04/percona-everest_hu_ec17cec384b24f82.webp 1223w" src="https://percona.community/blog/2024/04/percona-everest_hu_ec17cec384b24f82.webp" alt="Percona Everest" loading="lazy" />&lt;/figure>
Let’s start it!&lt;/p>
&lt;h2 id="install-wsl">Install WSL&lt;/h2>
&lt;p>We will use Kubectl to run commands on our Kubernetes clusters. There are many ways to use Kubectl on Windows.&lt;/p>
&lt;p>I will use WSL (Windows Subsystem for Linux) to use the Linux environment directly on Windows. It is beneficial because “kubectl” and other Kubernetes tools often have better support.&lt;/p>
&lt;p>In Windows 11, open PowerShell as Administrator and run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wsl --install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command will install WSL using the default options, including Ubuntu distribution and enabling the WSL 2 version.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1365" height="582" sizes="(max-width: 1365px) 100vw, 1365px" srcset="https://percona.community/blog/2024/04/pe-installing-wsl_hu_e158b7e657b7161b.webp 768w, https://percona.community/blog/2024/04/pe-installing-wsl_hu_4e66f87c814dc1da.webp 480w, https://percona.community/blog/2024/04/pe-installing-wsl_hu_6955bff555247482.webp 1365w" src="https://percona.community/blog/2024/04/pe-installing-wsl_hu_6955bff555247482.webp" alt="WSL Installing" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Then restart your computer and open the newly installed Linux distribution from the Start menu.
Complete the initial setup by creating a user account and password. Then update and upgrade your Linux distribution:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt update &lt;span class="o">&amp;&amp;&lt;/span> sudo apt upgrade&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Woolaa! We have Ubuntu running on Windows!
&lt;figure>&lt;img width="1600" height="777" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-installed-wsl_hu_b5b16b48aa101fb.webp 768w, https://percona.community/blog/2024/04/pe-installed-wsl_hu_d6ccc34173f45aff.webp 480w, https://percona.community/blog/2024/04/pe-installed-wsl_hu_9d7d875305bc1fba.webp 1400w" src="https://percona.community/blog/2024/04/pe-installed-wsl_hu_9d7d875305bc1fba.webp" alt="WSL Installed" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Installing WSL allows your Windows machine to run kubectl and other Linux-only applications smoothly. This setup is beneficial for developers and system administrators who work with both Windows and Linux systems.&lt;/p>
&lt;h2 id="install-kubectl">Install Kubectl&lt;/h2>
&lt;p>In our Ubuntu terminal on Windows, we will use official documentation to install it using the native package management system.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Download the latest release with the command:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -LO &lt;span class="s2">"https://dl.k8s.io/release/&lt;/span>&lt;span class="k">$(&lt;/span>curl -L -s https://dl.k8s.io/release/stable.txt&lt;span class="k">)&lt;/span>&lt;span class="s2">/bin/linux/amd64/kubectl"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install kubectl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo install -o root -g root -m &lt;span class="m">0755&lt;/span> kubectl /usr/local/bin/kubectl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Test to ensure the version you installed is up-to-date:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl version --client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="creating-a-kubernetes-cluster-in-google-cloud">Creating a Kubernetes Cluster in Google Cloud&lt;/h2>
&lt;p>To create a Kubernetes Cluster with GKE, you need to have access to Google Cloud. Ensure it functions correctly and that you can access your Google project and create Kubernetes clusters. Also, ensure you have the gke-gcloud-auth-plugin installed. You can check if this is installed by running the “gcloud components list” command. If it is not installed, follow the &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl" target="_blank" rel="noopener noreferrer">official documentation&lt;/a>.&lt;/p>
&lt;p>I have already set it up. Now, I will proceed to create my Kubernetes cluster.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud container clusters create percona-everest --zone europe-west2-c --machine-type n1-standard-4 --num-nodes&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="install-percona-everest">Install Percona Everest&lt;/h2>
&lt;p>A prerequisite for installing Percona Everest is having a Kubernetes cluster. I have one that I created with GKE. To verify the Kubernetes cluster, run the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-percona-everest-default-pool-1f7a9664-b3hd Ready &lt;none> 1h11m v1.27.8-gke.1067004
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-percona-everest-default-pool-1f7a9664-b5c3 Ready &lt;none> 1h11m v1.27.8-gke.1067004
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-percona-everest-default-pool-1f7a9664-nck4 Ready &lt;none> 1h11m v1.27.8-gke.1067004&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Before running the commands in the Installation section, note that Everest will search for the kubeconfig file at the ~/.kube/config path&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">KUBECONFIG&lt;/span>&lt;span class="o">=&lt;/span>~/.kube/config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The time to install Percona Everest. To install it, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -sfL &lt;span class="s2">"https://raw.githubusercontent.com/percona/everest/v0.9.1/install.sh"&lt;/span> &lt;span class="p">|&lt;/span> bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After installing it, you will see an output similar to the one on the left. In your browser, you can directly open 127.0.0.0:8080. Voilà! We now have Percona Everest up and running!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="898" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-login_hu_1cfd17d4343a5430.webp 768w, https://percona.community/blog/2024/04/pe-login_hu_f3ca109fc3fe49d3.webp 480w, https://percona.community/blog/2024/04/pe-login_hu_c6764924bfd181cd.webp 1400w" src="https://percona.community/blog/2024/04/pe-login_hu_c6764924bfd181cd.webp" alt="Percona Everest Login" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As the output indicates, the Percona Everest app will be available at http://127.0.0.1:8080. We use the authorization token to access the Everest UI and API.&lt;/p>
&lt;p>We don’t have a database, so let’s create a new one!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="698" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-first_hu_bb80665c5c14d12a.webp 768w, https://percona.community/blog/2024/04/pe-first_hu_4801a5f0b8a01653.webp 480w, https://percona.community/blog/2024/04/pe-first_hu_7d1beb802512c14d.webp 1400w" src="https://percona.community/blog/2024/04/pe-first_hu_7d1beb802512c14d.webp" alt="Percona Everest Create Database" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is the amazing thing about Percona Everest… you can create MySQL, MongoDB, and PostgreSQL databases on Kubernetes! Woohoo!!!
&lt;figure>&lt;img width="1600" height="771" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-second_hu_f91ec621d1ab38ff.webp 768w, https://percona.community/blog/2024/04/pe-second_hu_63adaa4f66d46592.webp 480w, https://percona.community/blog/2024/04/pe-second_hu_60327c6e05f63650.webp 1400w" src="https://percona.community/blog/2024/04/pe-second_hu_60327c6e05f63650.webp" alt="Percona Everest Databases" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You can configure the resources for a new database, set up backups, monitoring, point-in-time recovery, and more:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="612" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-third_hu_bbd49b02ac8100f9.webp 768w, https://percona.community/blog/2024/04/pe-third_hu_efc8a5dd64e7847b.webp 480w, https://percona.community/blog/2024/04/pe-third_hu_f7cd01c6af779cd4.webp 1400w" src="https://percona.community/blog/2024/04/pe-third_hu_f7cd01c6af779cd4.webp" alt="Percona Everest Screen" loading="lazy" />&lt;/figure>
And this is how it looks: your database is in Kubernetes!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="437" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/04/pe-last_hu_f3245b06aa2c34b4.webp 768w, https://percona.community/blog/2024/04/pe-last_hu_c60bb85169c0a43d.webp 480w, https://percona.community/blog/2024/04/pe-last_hu_efcbf4b0f52d6fe6.webp 1400w" src="https://percona.community/blog/2024/04/pe-last_hu_efcbf4b0f52d6fe6.webp" alt="Percona Everest Details" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Deploying Percona Everest on GCP using kubectl from a Windows 11 platform demonstrates the versatility and robust capabilities of managing databases on Kubernetes. The process should help you set up a powerful cloud-native database platform efficiently. We’ve walked through setting up your environment, installing necessary tools, creating a Kubernetes cluster, and finally deploying Percona Everest. Now, you can take full advantage of everything Percona Everest offers, from operational flexibility to cost efficiency.&lt;/p>
&lt;p>If Percona Everest seems cool, feel free to contribute—it’s open source! Find &lt;a href="https://github.com/percona/everest" target="_blank" rel="noopener noreferrer">Percona Everest on GitHub&lt;/a>. If you encounter any issues during installation or have more questions, write to us in our &lt;a href="https://forums.percona.com/c/percona-everest/81" target="_blank" rel="noopener noreferrer">community forum&lt;/a>. If you prefer learning visually through videos, we have a friendly &lt;a href="https://www.youtube.com/watch?v=vxhNon-el9Q&amp;list=PLWhC0zeznqkny4ehPTejdPwCnZ_RS3_Np" target="_blank" rel="noopener noreferrer">playlist of Percona Everest&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>Percona Everest</category><category>Windows</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2024/04/percona-everest_hu_491ab6499dd461e1.jpg"/><media:content url="https://percona.community/blog/2024/04/percona-everest_hu_7a2db783af9d6008.jpg" medium="image"/></item><item><title>Release Roundup April 17, 2024</title><link>https://percona.community/blog/2024/04/17/release-roundup-april-17-2024/</link><guid>https://percona.community/blog/2024/04/17/release-roundup-april-17-2024/</guid><pubDate>Wed, 17 Apr 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates April 2 - April 17, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates April 2 - April 17, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes those releases and updates that have come out since April 1, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-distribution-for-mysql-8036-pxc-based-variant">Percona Distribution for MySQL 8.0.36 (PXC-based variant)&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mysql/8.0/release-notes-pxc-v8.0.36.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MySQL 8.0.36 (PXC-based variant)&lt;/a> was released on April 3, 2024. It is the most stable, scalable, and secure open source MySQL distribution, with two download options: one based on Percona Server for MySQL and one based on Percona XtraDB Cluster. This release is focused on the Percona XtraDB Cluster-based deployment variation and is based on Percona XtraDB Cluster 8.0.36. Release highlights include improvements and bug fixes provided by Oracle for MySQL 8.0.36:&lt;/p>
&lt;ul>
&lt;li>The hashing algorithm employed yielded poor performance when using a HASH field to check for uniqueness. (Bug #109548, Bug #34959356)&lt;/li>
&lt;li>All statement instrument elements that begin with &lt;code>statement/sp/%&lt;/code>, except &lt;code>statement/sp/stmt&lt;/code>, are disabled by default.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mysql/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MySQL 8.0.36 (PXC-based variant)&lt;/a>&lt;/p>
&lt;h2 id="percona-xtradb-cluster-8036">Percona XtraDB Cluster 8.0.36&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-xtradb-cluster/8.0/release-notes/8.0.36-28.html" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster 8.0.36&lt;/a> was released on April 3, 2024. It supports critical business applications in your public, private, or hybrid cloud environment. Our free, open source, enterprise-grade solution includes the high availability and security features your business requires to meet your customer expectations and business goals.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">Download Percona XtraDB Cluster 8.0.36&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-707">Percona Distribution for MongoDB 7.0.7&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/7.0/release-notes-v7.0.7.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 7.0.7&lt;/a> was released on April 4, 2024. It is a freely available MongoDB database alternative, giving you a single solution that combines enterprise components from the open source community, designed and tested to work together.&lt;/p>
&lt;p>It includes the following components:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Percona Server for MongoDB&lt;/em> is a fully compatible source-available, drop-in replacement for MongoDB.&lt;/li>
&lt;li>&lt;em>Percona Backup for MongoDB&lt;/em> is a distributed, low-impact solution for achieving consistent backups of MongoDB sharded clusters and replica sets.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 7.0.7&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-5026">Percona Distribution for MongoDB 5.0.26&lt;/h2>
&lt;p>April 9, 2024, saw the release of &lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/5.0/release-notes-v5.0.26.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 5.0.26&lt;/a>. This release is based on Percona Server for MongoDB 5.0.26-22 and Percona Backup for MongoDB 2.4.1.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 5.0.26&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-4429">Percona Distribution for MongoDB 4.4.29&lt;/h2>
&lt;p>On April 2, 2024, we released &lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/4.4/release-notes-v4.4.29.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 4.4.29.&lt;/a> This is the last minor release in Percona Distribution for MongoDB 4.4.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 4.4.29&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-707-4">Percona Server for MongoDB 7.0.7-4&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/7.0/release_notes/7.0.7-4.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 7.0.7-4&lt;/a> was released on April 4, 2024. It is an enhanced, source-available, and highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB Community Edition 7.0.5 and includes the improvements and bug fixes of MongoDB 7.0.6 Community Edition and MongoDB 7.0.7 Community Edition.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 7.0.7-4&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-5026-22">Percona Server for MongoDB 5.0.26-22&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/5.0/release_notes/5.0.26-22.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 5.0.26-22&lt;/a> was released on April 9, 2024. It is an enhanced, source-available, and highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB 5.0.x Community Edition. Percona Server for MongoDB 5.0.26-22 includes both improvements and bug fixes of MongoDB 5.0.25 Community Edition and MongoDB 5.0.26 Community Edition.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 5.0.26-22&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-4429-28">Percona Server for MongoDB 4.4.29-28&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/4.4/release_notes/4.4.29-28.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 4.4.29-28&lt;/a> was released on April 2, 2024. This is the last minor release in Percona Server for MongoDB 4.4.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 4.4.29-28&lt;/a>&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Opensource</category><category>XtraDB</category><category>MongoDB</category><category>MySQL</category><category>Releases</category><media:thumbnail url="https://percona.community/blog/2024/04/Roundup-April-17_hu_4fafaeddff437b97.jpg"/><media:content url="https://percona.community/blog/2024/04/Roundup-April-17_hu_b3bd42422b7c7b5e.jpg" medium="image"/></item><item><title>Release Roundup April 2, 2024</title><link>https://percona.community/blog/2024/04/02/release-roundup-april-2-2024/</link><guid>https://percona.community/blog/2024/04/02/release-roundup-april-2-2024/</guid><pubDate>Tue, 02 Apr 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates March 18 - April 2, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates March 18 - April 2, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes those releases and updates that have come out since March 18, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-monitoring-and-management-2412">Percona Monitoring and Management 2.41.2&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/release-notes/2.41.2.html" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management 2.41.2&lt;/a> was released on March 22, 2024. It is an open source database monitoring, management, and observability solution for MySQL, PostgreSQL, and MongoDB. Starting with PMM 2.41.2, we now offer pmm-client packages for the latest version of Debian. You can install these packages by following the instructions in our documentation. We have also added several experimental dashboards, which are subject to change and recommended for testing purposes only.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Download Percona Monitoring and Management 2.41.2&lt;/a>&lt;/p>
&lt;h2 id="percona-operator-for-mysql-based-on-percona-server-for-mysql-070">Percona Operator for MySQL based on Percona Server for MySQL 0.7.0&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mysql/ps/ReleaseNotes/Kubernetes-Operator-for-PS-RN0.7.0.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL based on Percona Server for MySQL 0.7.0&lt;/a> was released on March 25, 2024. Percona Operator for MySQL allows users to deploy MySQL clusters with both asynchronous and group replication topology. This release includes various stability improvements and bug fixes, getting the Operator closer to the General Availability stage. Version 0.7.0 of the Percona Operator for MySQL is still a tech preview release and it is not recommended for production environments.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-operator-for-mysql" target="_blank" rel="noopener noreferrer">Download Percona Operator for MySQL based on Percona Server for MySQL 0.7.0&lt;/a>&lt;/p>
&lt;h2 id="percona-xtrabackup-830-1">Percona XtraBackup 8.3.0-1&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-xtrabackup/innovation-release/release-notes/8.3.0-1.html" target="_blank" rel="noopener noreferrer">Percona XtraBackup 8.3.0-1&lt;/a> was released on March 26, 2024. Percona XtraBackup 8.3.0-1 is based on MySQL 8.3 and fully supports the Percona Server for MySQL 8.3 Innovation series and the MySQL 8.3 Innovation series. This release allows taking backups of Percona Server 8.3.0-1 and MySQL 8.3. This Innovation release is only supported for a short time and is designed to be used in an environment with fast upgrade cycles. Developers and DBAs are exposed to the latest features and improvements. Patches and security fixes are included in the next Innovation release instead of a patch release or fix release within an Innovation release. To keep your environment current with the latest patches or security fixes, upgrade to the latest release.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-xtrabackup" target="_blank" rel="noopener noreferrer">Download Percona XtraBackup 8.3.0-1&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-6014">Percona Distribution for MongoDB 6.0.14&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/6.0/release-notes-v6.0.14.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 6.0.14&lt;/a> was released on March 26, 2024. It is a freely available MongoDB database alternative, giving you a single solution that combines enterprise components from the open source community, designed and tested to work together. Release highlights include:&lt;/p>
&lt;ul>
&lt;li>Fixed the issue with missing peer certificate validation if neither CAFile nor clusterCAFile is provided.&lt;/li>
&lt;li>Fixed the issue with multi-document transactions missing documents when the movePrimary operation runs concurrently by detecting placement conflicts in multi-document transactions.&lt;/li>
&lt;li>Allow a clustered index scan in a clustered collection if a notablescan option is enabled.&lt;/li>
&lt;li>Fixed tracking memory usage in SharedBufferFragment to prevent out of memory issues in the WiredTiger storage engine.&lt;/li>
&lt;li>Added an index on the process field for the &lt;code>config.locks&lt;/code> collection to ensure update operations on it are completed even in heavy loaded deployments.&lt;/li>
&lt;li>Fixed the incorrect hardware checksum calculation on zSeries for buffers on stack.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mongodb" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 6.0.14&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-6014-11">Percona Server for MongoDB 6.0.14-11&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/6.0/release_notes/6.0.14-11.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 6.0.14-11&lt;/a> was released on March 26, 2024. It is an enhanced, source-available, and highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB Community Edition 6.0.14. It is based on MongoDB 6.0.14 Community Edition and includes improvements and bug fixes provided by MongoDB.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 6.0.14-11&lt;/a>&lt;/p>
&lt;h2 id="percona-backup-for-mongodb-241">Percona Backup for MongoDB 2.4.1&lt;/h2>
&lt;p>On March 25, 2024, &lt;a href="https://docs.percona.com/percona-backup-mongodb/release-notes/2.4.1.html" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB 2.4.1&lt;/a> was released. It is a distributed, low-impact solution for consistent backups of MongoDB sharded clusters and replica sets. This is a tool for creating consistent backups across a MongoDB sharded cluster (or a non-sharded replica set), and for restoring those backups to a specific point in time.&lt;/p>
&lt;p>This release fixes the issue of failing incremental backups. It was caused by the backup metadata document reaching the maximum size limit of 16MB. The issue is fixed by introducing the new approach to handling the metadatada document: it no longer contains the list of backup files which is now stored separately on the storage and is read by PBM on demand. The new metadata handling approach applies to physical, incremental, and snapshot-based backups.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Backup for MongoDB 2.4.1&lt;/a>&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Percona</category><category>Opensource</category><category>XtraBackup</category><category>MongoDB</category><category>MySQL</category><category>PMM</category><category>Releases</category><media:thumbnail url="https://percona.community/blog/2024/04/Roundup-April-2_hu_3665416025618530.jpg"/><media:content url="https://percona.community/blog/2024/04/Roundup-April-2_hu_684b0754b818da96.jpg" medium="image"/></item><item><title>Creating a Standby Cluster With the Percona Operator for PostgreSQL</title><link>https://percona.community/blog/2024/03/27/creating-a-standby-cluster-with-the-percona-operator-for-postgresql/</link><guid>https://percona.community/blog/2024/03/27/creating-a-standby-cluster-with-the-percona-operator-for-postgresql/</guid><pubDate>Wed, 27 Mar 2024 00:00:00 UTC</pubDate><description>In this video, Nickolay Ihalainen, a Senior Scaling Specialist at Percona Global Services, explains how to set up replication with standby clusters for Kubernetes databases using Percona’s open-source tools, including the Percona Operator for PostgreSQL</description><content:encoded>&lt;p>In this video, &lt;a href="https://www.linkedin.com/in/nickolay-ihalainen-b8a35838/?originalSubdomain=ru" target="_blank" rel="noopener noreferrer">Nickolay Ihalainen&lt;/a>, a Senior Scaling Specialist at Percona Global Services, explains how to set up replication with standby clusters for Kubernetes databases using Percona’s open-source tools, including the &lt;a href="https://www.percona.com/postgresql" target="_blank" rel="noopener noreferrer">Percona Operator for PostgreSQL&lt;/a>&lt;/p>
&lt;p>A &lt;strong>Standby Cluster&lt;/strong> is a backup version of your main database. It’s there to keep your data safe and make sure your database can keep running even if something goes wrong with the main one.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1074" height="583" sizes="(max-width: 1074px) 100vw, 1074px" srcset="https://percona.community/blog/2024/03/standby_hu_a8fad9827aa5c78d.webp 768w, https://percona.community/blog/2024/03/standby_hu_1e398f4bcb7016ef.webp 480w, https://percona.community/blog/2024/03/standby_hu_b179bec9d1798abc.webp 1074w" src="https://percona.community/blog/2024/03/standby_hu_b179bec9d1798abc.webp" alt="Percona Demo for StandBy Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For this demo, we use &lt;strong>Percona Operators for PostgreSQL&lt;/strong> to create the clusters, which facilitates high availability setups and database management by automating deployment, scaling, and management tasks within Kubernetes environments.&lt;/p>
&lt;p>Nickolay created a primary node and the configuration of replication to standby clusters for PostgreSQL, ensuring data redundancy and availability. This primary node has two standby databases in the same primary node.&lt;/p>
&lt;p>Then, we have the &lt;strong>object storage (S3)&lt;/strong> for backups, highlighting the importance of having offsite backups in different geographical locations to safeguard against data loss. This is where the primary node will access to make a data replication.&lt;/p>
&lt;p>This demo also includes using &lt;strong>Patroni&lt;/strong> to manage this process, enabling replication and failover between primary and standby servers, and &lt;strong>PgBouncer&lt;/strong>, a tool that manages how applications are aligned to communicate with a PostgreSQL database.&lt;/p>
&lt;p>Watch our complete hands on in this YouTube video:&lt;/p>
&lt;br />
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/nqeGvvZ5G5Y?si=n3ho7xHJiT6F8u9v" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>&lt;/iframe>
&lt;p>You can find more instructions on how to &lt;a href="https://docs.percona.com/percona-operator-for-postgresql/2.0/standby.html" target="_blank" rel="noopener noreferrer">deploy a standby cluster for Disaster Recovery&lt;/a>, and also you can &lt;a href="https://www.percona.com/blog/creating-a-standby-cluster-with-the-percona-distribution-for-postgresql-operator/" target="_blank" rel="noopener noreferrer">Create a Standby Cluster With the Percona Operator for PostgreSQL&lt;/a>.&lt;/p>
&lt;p>Is you have questions o feedback, write to us in our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Comunity Forum&lt;/a>&lt;/p></content:encoded><author>Zsolt Parragi</author><author>Edith Puclla</author><category>PostgreSQL</category><category>Backups</category><category>Percona</category><category>pg_zsolt</category><media:thumbnail url="https://percona.community/blog/2024/03/standby_hu_bb4bc2d9eb612df1.jpg"/><media:content url="https://percona.community/blog/2024/03/standby_hu_2e050ddc08ff64b3.jpg" medium="image"/></item><item><title>Open Door MongoDB Chats</title><link>https://percona.community/blog/2024/03/26/open-door-mongodb-chats/</link><guid>https://percona.community/blog/2024/03/26/open-door-mongodb-chats/</guid><pubDate>Tue, 26 Mar 2024 00:00:00 UTC</pubDate><description>Let’s talk about MongoDB! This week, Jan Wieremjewicz, Senior Product Manager, and Michal Nosek, Pre-Sales Enterprise Architect at Percona, made two series of videos covering basic to advanced concepts in MongoDB and backups. Le’s explore them together!</description><content:encoded>&lt;p>Let’s talk about MongoDB!
This week, &lt;strong>Jan Wieremjewicz&lt;/strong>, Senior Product Manager, and &lt;strong>Michal Nosek&lt;/strong>, Pre-Sales Enterprise Architect at &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a>, made two series of videos covering basic to advanced concepts in &lt;strong>MongoDB&lt;/strong> and &lt;strong>backups&lt;/strong>.
&lt;br />
&lt;br />
Le’s explore them together!&lt;/p>
&lt;h2 id="enterprise-scale-backups-for-mongodb-with-open-source-software">Enterprise Scale Backups for MongoDB with Open Source Software&lt;/h2>
&lt;p>In the first video about enterprise-scale backups for MongoDB using open-source software, Jan and Michal discuss the challenges and solutions associated with backing up large datasets in MongoDB. They explore the difficulties of managing backups and restorations for huge data sizes, emphasizing the limitations of logical backups like &lt;strong>mongodump&lt;/strong> and &lt;strong>mongorestore&lt;/strong>, which can be time-consuming for large datasets.&lt;/p>
&lt;p>They also highlight alternative solutions, including MongoDB Enterprise licenses offering robust backup tools, the use of storage-level snapshots, and &lt;a href="https://docs.percona.com/percona-backup-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona’s own backup solution for MongoDB&lt;/a>. This solution aims to simplify and speed up the backup and restoration process, especially for large, sharded clusters, and is designed to be efficient and flexible, preventing vendor lock-in and helping with enterprise needs.&lt;/p>
&lt;p>The video covers the technical aspects of ensuring consistent, performant backups across complex database architectures. I recommend you watch the video because it covers basic topics from an expert viewpoint.&lt;/p>
&lt;br />
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/sO-43bxaf7k?si=zgjHA6otYTmhBtYp" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>&lt;/iframe>
&lt;h2 id="optimize-the-rpo-and-rto-for-your-mongodb-backup-strategy">Optimize the RPO and RTO for your MongoDB Backup Strategy&lt;/h2>
&lt;p>In this second episode of their discussion on &lt;strong>MongoDB backups&lt;/strong>, Jan and Michal explore the critical aspects of developing a comprehensive backup strategy, focusing on &lt;strong>Recovery Point Objective(RPO)&lt;/strong> and &lt;strong>Recovery Time Objective (RTO)&lt;/strong>. They highlight the importance of these concepts in determining the acceptable amount of data loss and system downtime in the event of a failure. Michal explains that achieving a near-zero RPO requires significant investment, while Jan highlights the importance of documenting a backup strategy for job security and organizational awareness.&lt;/p>
&lt;p>They also discuss the common oversight among smaller organizations that lack a formal backup strategy, often due to the perceived cost or a lack of major failures. The conversation covers the necessity of regularly testing backups and restore times, as well as communicating the current backup and recovery capabilities to higher management to align expectations with business criticality. This approach ensures transparency and preparedness for potential system failures, making it an essential practice for companies of all sizes.&lt;/p>
&lt;br />
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/kAyGieor0Q0?si=KB_BGfyjNiCYe624" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>&lt;/iframe>
&lt;p>Read our latest article titled &lt;a href="https://www.percona.com/blog/using-percona-backup-for-mongodb-in-replica-set-and-sharding-environment-part-one/" target="_blank" rel="noopener noreferrer">Using Percona Backup for MongoDB in Replica Set and Sharding Environments: Part One&lt;/a>&lt;/p>
&lt;p>For more information, visit the official &lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB website&lt;/a>. If you have any questions, feel free to visit our forum under the MongoDB category.&lt;/p></content:encoded><author>Edith Puclla</author><category>MongoDB</category><category>Backups</category><category>Percona</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2024/03/mongodb-open-doors_hu_55f124c3e7149a31.jpg"/><media:content url="https://percona.community/blog/2024/03/mongodb-open-doors_hu_59086292792b9b4d.jpg" medium="image"/></item><item><title>Release Roundup March 18, 2024</title><link>https://percona.community/blog/2024/03/18/release-roundup-march-18-2024/</link><guid>https://percona.community/blog/2024/03/18/release-roundup-march-18-2024/</guid><pubDate>Mon, 18 Mar 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates March 5 - March 18, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates March 5 - March 18, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes those releases and updates that have come out since March 4, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-server-for-mysql-5744-49-post-eol-support-version">Percona Server for MySQL 5.7.44-49 (Post-EOL support version)&lt;/h2>
&lt;p>This release is &lt;a href="https://docs.percona.com/percona-server/5.7/release-notes/5.7.44-49.html" target="_blank" rel="noopener noreferrer">Percona Server for MySQL 5.7.44-49 (Post-EOL support version)&lt;/a>, and the fixes are available to &lt;a href="https://www.percona.com/post-mysql-5-7-eol-support" target="_blank" rel="noopener noreferrer">MySQL 5.7 Post-EOL Support from Percona customers&lt;/a>. Community members can &lt;a href="https://docs.percona.com/percona-server/5.7/installation/git-source-tree.html" target="_blank" rel="noopener noreferrer">build this release from the source&lt;/a>. Percona Server for MySQL 5.7.44-49 contains the fix for &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-20963" target="_blank" rel="noopener noreferrer">CVE-2024-20963&lt;/a> and a portability fix.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/downloads#percona-server-mysql" target="_blank" rel="noopener noreferrer">Download Percona Server for MySQL 5.7.44-49 (Post-EOL support version)&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mysql-8036-ps-based-variant">Percona Distribution for MySQL 8.0.36 (PS-based variant)&lt;/h2>
&lt;p>On March 4, 2024, &lt;a href="https://docs.percona.com/percona-distribution-for-mysql/8.0/release-notes-ps-v8.0.36.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MySQL 8.0.36 (PS-based variant)&lt;/a> was released. It is the most stable, scalable, and secure open source MySQL distribution, with two download options: one based on Percona Server for MySQL and one based on Percona XtraDB Cluster. This release is focused on the Percona Server for MySQL-based deployment variation. This release fixes the Orchestrator issues.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MySQL 8.0.36 (PS-based variant)&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mysql-8036">Percona Server for MySQL 8.0.36&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server/8.0/release-notes/8.0.36-28.html" target="_blank" rel="noopener noreferrer">Percona Server for MySQL 8.0.36&lt;/a> was released on March 4, 2024. It includes all the features and bug fixes available in the MySQL 8.0.36 Community Edition, and enterprise-grade features developed by Percona. Improvements and bug fixes provided by Oracle for MySQL 8.0.36 and included in Percona Server for MySQL are the following:&lt;/p>
&lt;ul>
&lt;li>The hashing algorithm employed yielded poor performance when using a HASH field to check for uniqueness. (Bug #109548, Bug #34959356)&lt;/li>
&lt;li>All statement instrument elements that begin with &lt;code>statement/sp/%&lt;/code>, except &lt;code>statement/sp/stmt&lt;/code>, are disabled by default.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-server-for-mysql" target="_blank" rel="noopener noreferrer">Download Percona Server for MySQL 8.0.36&lt;/a>&lt;/p>
&lt;h2 id="percona-operator-for-mysql-based-on-percona-xtradb-cluster-1140">Percona Operator for MySQL based on Percona XtraDB Cluster 1.14.0&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-operator-for-mysql/pxc/ReleaseNotes/Kubernetes-Operator-for-PXC-RN1.14.0.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL based on Percona XtraDB Cluster 1.14.0&lt;/a> was released on March 4, 2024. It contains everything you need to quickly and consistently deploy and scale Percona XtraDB Cluster instances in a Kubernetes-based environment on-premises or in the cloud. Among other new features, a custom prefix for Percona Monitoring and Management (PMM) allows using one PMM Server to monitor multiple databases, even if they have identical cluster names.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mysql/software/percona-operator-for-mysql" target="_blank" rel="noopener noreferrer">Download Percona Operator for MySQL based on Percona XtraDB Cluster 1.14.0&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql-1314">Percona Distribution for PostgreSQL 13.14&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/postgresql/13/release-notes-v13.14.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 13.14&lt;/a> was released on March 6, 2024. It is a solution of a collection of tools from the PostgreSQL community that are tested to work together and assist you in deploying and managing PostgreSQL. A release highlight is that the Docker image for Percona Distribution for PostgreSQL is now available for ARM architectures. This improves the user experience with the Distribution for developers with ARM-based workstations.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/postgresql/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Download Percona Distribution for PostgreSQL 13.14&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql-1218">Percona Distribution for PostgreSQL 12.18&lt;/h2>
&lt;p>On March 11, 2024, we released &lt;a href="https://docs.percona.com/postgresql/12/release-notes-v12.18.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 12.18. &lt;/a>This release of Percona Distribution for PostgreSQL is based on PostgreSQL 12.18&lt;a href="https://docs.percona.com/postgresql/12/release-notes-v12.18.html" target="_blank" rel="noopener noreferrer">.&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/postgresql/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Download Percona Distribution for PostgreSQL 12.18&lt;/a>&lt;/p>
&lt;h2 id="percona-backup-for-mongodb-240">Percona Backup for MongoDB 2.4.0&lt;/h2>
&lt;p>On March 5, 2024, &lt;a href="https://docs.percona.com/percona-backup-mongodb/release-notes/2.4.0.html" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB 2.4.0&lt;/a> was released. It is a distributed, low-impact solution for consistent backups of MongoDB sharded clusters and replica sets. This is a tool for creating consistent backups across a MongoDB sharded cluster (or a non-sharded replica set), and for restoring those backups to a specific point in time. A release highlight is that you can now &lt;a href="https://docs.percona.com/percona-backup-mongodb/usage/delete-backup.html#__tabbed_2_3" target="_blank" rel="noopener noreferrer">delete backup snapshots of a specific type&lt;/a>. For example, delete only logical backups that you might have created and no longer need. You can also check what exactly will be deleted with the new &lt;code>--dry-run flag&lt;/code>. This improvement helps you better meet the organization’s backup policy and improves your experience with cleaning up outdated data.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-backup-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Backup for MongoDB 2.4.0&lt;/a>&lt;/p>
&lt;hr />
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Opensource</category><category>PostgreSQL</category><category>MongoDB</category><category>MySQL</category><category>Releases</category><media:thumbnail url="https://percona.community/blog/2024/03/Roundup-March-18_hu_95f64134d084da08.jpg"/><media:content url="https://percona.community/blog/2024/03/Roundup-March-18_hu_470021b41db4fe23.jpg" medium="image"/></item><item><title>Release Roundup March 4, 2024</title><link>https://percona.community/blog/2024/03/04/release-roundup-march-4-2024/</link><guid>https://percona.community/blog/2024/03/04/release-roundup-march-4-2024/</guid><pubDate>Mon, 04 Mar 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates February 21 - March 4, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates February 21 - March 4, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes those releases and updates that have come out since February 20, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql-162">Percona Distribution for PostgreSQL 16.2&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/postgresql/16/release-notes-v16.2.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 16.2&lt;/a> was released on February 27, 2024. It provides the best and most critical enterprise components from the open source community in a single distribution, designed and tested to work together. This release is based on PostgreSQL 16.2. A release highlight is that a Docker image for Percona Distribution for PostgreSQL is now available for ARM architectures. This improves the user experience with the Distribution for developers with ARM-based workstations.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/postgresql/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Download Percona Distribution for PostgreSQL 16.2&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql-156">Percona Distribution for PostgreSQL 15.6&lt;/h2>
&lt;p>On February 28, 2024, we released &lt;a href="https://docs.percona.com/postgresql/15/release-notes-v15.6.html#get-expert-help" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 15.6&lt;/a>, which is based on PostgreSQL 15.6. A Docker image for Percona Distribution for PostgreSQL is now available for ARM architectures. This improves the user experience with the Distribution for developers with ARM-based workstations.&lt;/p>
&lt;p>Percona Distribution for PostgreSQL also includes the following packages:&lt;/p>
&lt;ul>
&lt;li>&lt;code>llvm&lt;/code> 12.0.1 packages for Red Hat Enterprise Linux 8 and compatible derivatives. This fixes compatibility issues with LLVM from upstream.&lt;/li>
&lt;li>supplemental &lt;code>ETCD&lt;/code> packages which can be used for setting up Patroni clusters. These packages are available for the following operating systems:&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/postgresql/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Download Percona Distribution for PostgreSQL 15.6&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-postgresql-1411">Percona Distribution for PostgreSQL 14.11&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/postgresql/14/release-notes-v14.11.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 14.11&lt;/a> was released on March 1, 2024, and is based on PostgreSQL 14.11. A release highlight is a Docker image for Percona Distribution for PostgreSQL is now available for ARM architectures. This improves the user experience with the Distribution for developers with ARM-based workstations.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/postgresql/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Download Percona Distribution for PostgreSQL 14.11&lt;/a>&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Percona</category><category>opensource</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2024/03/Roundup-March-4_hu_8f318ec53f63be23.jpg"/><media:content url="https://percona.community/blog/2024/03/Roundup-March-4_hu_17618ba17fe96993.jpg" medium="image"/></item><item><title>Setting Up Your Environment for Kubernetes Operators Using Docker, kubectl, and k3d</title><link>https://percona.community/blog/2024/03/04/setting-up-your-environment-for-kubernetes-operators-using-docker-kubectl-and-k3d/</link><guid>https://percona.community/blog/2024/03/04/setting-up-your-environment-for-kubernetes-operators-using-docker-kubectl-and-k3d/</guid><pubDate>Mon, 04 Mar 2024 00:00:00 UTC</pubDate><description>If you are just starting out in the world of Kubernetes operators, like me, preparing the environment for their installation should be something we do with not much difficulty. This blog will quickly guide you in setting the minimal environment.</description><content:encoded>&lt;p>If you are just starting out in the world of Kubernetes operators, like me, preparing the environment for their installation should be something we do with not much difficulty. This blog will quickly guide you in setting the minimal environment.&lt;/p>
&lt;p>Kubernetes operators are invaluable for automating complex database operations, tasks that Kubernetes does not handle directly. Operators make it easy for us – they take care of essential tasks like &lt;strong>backups&lt;/strong> and &lt;strong>restores&lt;/strong>, which are crucial in database management.&lt;/p>
&lt;p>If you want an introduction to Kubernetes Operators, I cover it in this 5-minute blog post, &lt;a href="https://www.percona.com/blog/exploring-the-kubernetes-application-lifecycle-with-percona/" target="_blank" rel="noopener noreferrer">Exploring the Kubernetes Application Lifecycle With Percona&lt;/a>.&lt;/p>
&lt;p>Now that we know why Kubernetes Operators are essential let’s prepare our environment to install some of them. We are going to base this installation on Linux for now.
Prerequisites:
For this, we will need a basic understanding of Kubernetes concepts and some Linux command line skills.
We also need &lt;a href="https://docs.docker.com/engine/install/ubuntu/" target="_blank" rel="noopener noreferrer">Docker Engine&lt;/a> to be able to use K3d at all for containerization. To test, make sure this command runs appropriately:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run hello-world&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="installing-kubectl">Installing kubectl&lt;/h2>
&lt;p>To manage and deploy applications on Kubernetes, we will need &lt;strong>kubectl&lt;/strong> tool, which is included in most Kubernetes distributions. If it’s not installed, let’s do it following the &lt;a href="https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/" target="_blank" rel="noopener noreferrer">official installation instructions&lt;/a>:&lt;/p>
&lt;p>To install the &lt;strong>kubectl&lt;/strong> binary with curl on Linux, we need to download the latest release of kubectl using the command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -LO &lt;span class="s2">"https://dl.k8s.io/release/&lt;/span>&lt;span class="k">$(&lt;/span>curl -L -s https://dl.k8s.io/release/stable.txt&lt;span class="k">)&lt;/span>&lt;span class="s2">/bin/linux/amd64/kubectl"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The previous binary installs kubectl in /usr/local/bin/kubectl. We need root ownership and specific permissions for secure execution.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo install -o root -g root -m &lt;span class="m">0755&lt;/span> kubectl /usr/local/bin/kubectl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To test the installation, we use the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl version --client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl version --client --output&lt;span class="o">=&lt;/span>yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you receive a response like this, it indicates that you are ready to use &lt;code>kubectl&lt;/code>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">Client Version: v1.29.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="installing-k3d">Installing K3d&lt;/h2>
&lt;p>k3d is a lightweight tool that simplifies running k3s (Rancher Lab’s minimal Kubernetes distribution in Docker), enabling easy creation of single and multi-node k3s clusters for local development.&lt;/p>
&lt;p>Install the current latest release of k3d with curl:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh &lt;span class="p">|&lt;/span> bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To test the installation, you can use the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">k3d --help&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you see a message similar to this, you are ready to create your k3d Kubernetes clusters.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">https://k3d.io/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k3d is a wrapper CLI that helps you to easily create k3s clusters inside docker.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Nodes of a k3d cluster are docker containers running a k3s image.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">All Nodes of a k3d cluster are part of the same docker network.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Usage:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k3d &lt;span class="o">[&lt;/span>flags&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k3d &lt;span class="o">[&lt;/span>command&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Available Commands:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cluster Manage cluster&lt;span class="o">(&lt;/span>s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">completion Generate completion scripts &lt;span class="k">for&lt;/span> &lt;span class="o">[&lt;/span>bash, zsh, fish, powershell &lt;span class="p">|&lt;/span> psh&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">config Work with config file&lt;span class="o">(&lt;/span>s&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="starting-the-kubernetes-cluster">Starting the Kubernetes cluster&lt;/h2>
&lt;p>Let’s use K3d and create a Kubernetes cluster with three nodes. Using the flag -a, you can specify the number of nodes you want to add to the k3d cluster.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">k3d cluster create database-cluster -a &lt;span class="m">3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, list details for our k3d cluster.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">k3d cluster list database-cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME SERVERS AGENTS LOADBALANCER
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">database-cluster 1/1 3/3 true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, our environment is ready to begin installing our Percona Kubernetes Operators.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this tutorial, we chose k3d over Minikube due to its efficiency and speed in setting up Kubernetes clusters with multiple nodes, which are essential for effectively testing Kubernetes operators in a local environment. Although it’s possible to perform tests on a single node with both systems, k3d makes it easier to simulate a more realistic distributed environment, allowing us to utilize our resources more efficiently.&lt;/p>
&lt;p>Take a look at our GitHub repository for our Percona Kubernetes Operators:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mysql-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-postgresql-operator" target="_blank" rel="noopener noreferrer">Percona Operator for PostgreSQL&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>They are fully Open Source. And if you are looking for a version with a graphical interface, we have &lt;a href="https://docs.percona.com/everest/index.html" target="_blank" rel="noopener noreferrer">Percona Everest&lt;/a>, our cloud-native database platform: docs.percona.com/everest&lt;/p>
&lt;p>What’s Next? Let’s install our Kubernetes Operators!&lt;/p></content:encoded><author>Edith Puclla</author><category>edith_puclla</category><category>kubernetes</category><category>operators</category><category>k3d</category><category>docker</category><media:thumbnail url="https://percona.community/blog/2024/03/intro_hu_f38b7c56cf487f0.jpg"/><media:content url="https://percona.community/blog/2024/03/intro_hu_6fa6a90910cc0565.jpg" medium="image"/></item><item><title>Release Roundup February 21, 2024</title><link>https://percona.community/blog/2024/02/21/release-roundup-february-21-2024/</link><guid>https://percona.community/blog/2024/02/21/release-roundup-february-21-2024/</guid><pubDate>Wed, 21 Feb 2024 00:00:00 UTC</pubDate><description>Percona software releases and updates February 5 - February 21, 2024.</description><content:encoded>&lt;p>&lt;em>Percona software releases and updates February 5 - February 21, 2024.&lt;/em>&lt;/p>
&lt;p>Percona is a leading provider of unbiased, performance-first, open source database solutions that allow organizations to easily, securely, and affordably maintain business agility, minimize risks, and stay competitive, free from vendor lock-in. Percona software is designed for peak performance, uncompromised security, limitless scalability, and disaster-proofed availability.&lt;/p>
&lt;p>Our Release Roundups showcase the latest Percona software updates, tools, and features to help you manage and deploy our software. It offers highlights, critical information, links to the full release notes, and direct links to the software or service itself to download.&lt;/p>
&lt;p>Today’s post includes those releases and updates that have come out since February 5, 2024. Take a look.&lt;/p>
&lt;h2 id="percona-distribution-for-mysql-ps-based-variation-820">Percona Distribution for MySQL (PS-based variation) 8.2.0&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mysql/innovation-release/release-notes-ps-8.2.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MySQL (PS-based variation) 8.2.0&lt;/a> was released on February 5, 2024. It is a bundling of open source MySQL software enhanced with carefully curated and designed enterprise-grade features. Percona Distribution for MySQL offers two download options; this one is based on Percona Server for MySQL.&lt;/p>
&lt;p>This release merges the MySQL 8.2 code base, introducing several significant changes:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Removes remains of Percona-specific encryption features (support for custom Percona 5.7 encrypted binlog format).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Removes the deprecated &lt;code>rocksdb_strict_collation_check&lt;/code> and &lt;code>rocksdb_strict_collation_exceptions&lt;/code> RocksDB system variables.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mysql/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MySQL (PS-based variation) 8.2.0&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-6013">Percona Distribution for MongoDB 6.0.13&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/6.0/release-notes-v6.0.13.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 6.0.13&lt;/a> was released on February 20, 2024. It includes the following components:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Percona Server for MongoDB is a fully compatible source-available, drop-in replacement for MongoDB.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Percona Backup for MongoDB is a distributed, low-impact solution for achieving consistent backups of MongoDB sharded clusters and replica sets.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>This release of Percona Distribution for MongoDB is based on the production release of &lt;a href="https://docs.percona.com/percona-server-for-mongodb/6.0/release_notes/6.0.13-10.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 6.0.13-10&lt;/a> and &lt;a href="https://docs.percona.com/percona-backup-mongodb/release-notes/2.3.1.html" target="_blank" rel="noopener noreferrer">Percona Backup for MongoDB 2.3.1.&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 6.0.13&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-4428">Percona Distribution for MongoDB 4.4.28&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/4.4/release-notes-v4.4.28.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 4.4.28&lt;/a> was released on February 7, 2024. It’s a freely available MongoDB database alternative, giving you a single solution that combines enterprise components from the open source community, designed and tested to work together. In addition to bug fixes and improvements provided by MongoDB and included in Percona Server for MongoDB, Percona Backup for MongoDB 2.3.1 enhancements include the following:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Support for Percona Server for MongoDB 7.0.x&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The ability to define custom endpoints when using Microsoft Azure Blob Storage for backups&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Improved PBM Docker image to allow making physical backups with the shared mongodb data volume&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Updated Golang libraries that include fixes for the security vulnerability CVE-2023-39325.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>In addition, Percona Server for MongoDB 4.4.28-27 is no longer available on Ubuntu 18.04 (Bionic Beaver).&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 4.4.28&lt;/a>&lt;/p>
&lt;h2 id="percona-distribution-for-mongodb-4225">Percona Distribution for MongoDB 4.2.25&lt;/h2>
&lt;p>On February 8, 2024, &lt;a href="https://docs.percona.com/percona-distribution-for-mongodb/4.2/release-notes-v4.2.25.html" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB 4.2.25&lt;/a> was released. Release highlights include:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Optimized the construction of the balancer’s collection distribution status histogram&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Fixed the query planner logic to distinguish parameterized queries in the presence of a partial index that contains logical expressions ($and, $or).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Improved performance of updating the routing table and prevented blocking client requests during refresh for clusters with 1 million of chunks.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Avoided traversing routing table in balancer split chunk policy&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Fixed the issue that caused the modification of the original ChunkMap vector during the chunk migration and that could lead to data loss. The issue affects MongoDB versions 4.4.25, 5.0.21, 6.0.10 through 6.0.11 and 7.0.1 through 7.0.2. Requires stopping all chunk merge activities and restarting all the binaries in the cluster (both mongod and mongos).&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software" target="_blank" rel="noopener noreferrer">Download Percona Distribution for MongoDB 4.2.25&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-6013-10">Percona Server for MongoDB 6.0.13-10&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/6.0/release_notes/6.0.13-10.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 6.0.13-10&lt;/a> was released on February 20, 2024. It is based on MongoDB 6.0.13 Community Edition and supports the upstream protocols and drivers.&lt;/p>
&lt;p>Release highlights include:&lt;/p>
&lt;p>Percona Server for MongoDB packages are available for ARM64 architectures, enabling users to install it on-premises. The ARM64 packages are available for the following operating systems:&lt;/p>
&lt;ul>
&lt;li>Ubuntu 20.04 (Focal Fossa)&lt;/li>
&lt;li>Ubuntu 22.04 (Jammy Jellyfish)&lt;/li>
&lt;li>Red Hat Enterprise Linux 8 and compatible derivatives&lt;/li>
&lt;li>Red Hat Enterprise Linux 9 and compatible derivatives&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 6.0.13-10&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-4428-27">Percona Server for MongoDB 4.4.28-27&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/4.4/release_notes/4.4.28-27.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 4.4.28-27&lt;/a> was released on February 7, 2024. It is a source available, highly-scalable database that is a fully-compatible, drop-in replacement for MongoDB 4.4.28 Community Edition enhanced with enterprise-grade features. Release highlights include these bug fixes, provided by MongoDB and included in Percona Server for MongoDB:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Fixed the issue with the data and the ShardVersion mismatch for sharded multi-document transactions by adding the check that no chunk has moved for the collection being referenced since transaction started&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Improved cluster balancer performance by optimizing the construction of the balancer’s collection distribution status histogram&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Fixed the issue with blocking acquiring read/write tickets by TransactionCoordinator by validating that it can be recovered on step-up and can commit the transaction when there are no storage tickets available&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Investigated a solution to avoid a Full Time Diagnostic Data Capture (FTDC) mechanism to stall during checkpoint&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Percona Server for MongoDB 4.4.28-27 is no longer available on Ubuntu 18.04 (Bionic Beaver).&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Download Percona Server for MongoDB 4.4.28-27&lt;/a>&lt;/p>
&lt;h2 id="percona-server-for-mongodb-4225-25">Percona Server for MongoDB 4.2.25-25&lt;/h2>
&lt;p>&lt;a href="https://docs.percona.com/percona-server-for-mongodb/4.2/release_notes/4.2.25-25.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 4.2.25-25&lt;/a> was released on February 7, 2024. A release highlight is that Percona Server for MongoDB includes telemetry that fills in the gaps in our understanding of how you use Percona Server for MongoDB to improve our products. Participation in the anonymous program is optional. You can opt-out if you prefer not to share this information. Read more about Telemetry.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/mongodb/software/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB 4.2.25-25&lt;/a>&lt;/p>
&lt;p>That’s it for this roundup, and be sure to &lt;a href="https://twitter.com/Percona" target="_blank" rel="noopener noreferrer">follow us on Twitter&lt;/a> to stay up-to-date on the most recent releases! Percona is a leader in providing best-of-breed enterprise-class support, consulting, managed services, training, and software for MySQL, MongoDB, PostgreSQL, MariaDB, and other open source databases in on-premises and cloud environments and is trusted by global brands to unify, monitor, manage, secure, and optimize their database environments.&lt;/p></content:encoded><author>David Quilty</author><category>Percona</category><category>opensource</category><category>MySQL</category><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2024/02/Roundup-Feb-24_hu_4d9a09f09cf91615.jpg"/><media:content url="https://percona.community/blog/2024/02/Roundup-Feb-24_hu_8425b2ecff996dad.jpg" medium="image"/></item><item><title>Percona Bug Report: January 2024</title><link>https://percona.community/blog/2024/02/19/percona-bug-report-january-2024/</link><guid>https://percona.community/blog/2024/02/19/percona-bug-report-january-2024/</guid><pubDate>Mon, 19 Feb 2024 00:00:00 UTC</pubDate><description>At Percona, we believe that transparency is key to improving our products. We are dedicated to creating top-of-the-line open-source database solutions and providing support for any issues that may arise. We encourage feedback and bug reports to help us continually improve.</description><content:encoded>&lt;p>At Percona, we believe that transparency is key to improving our products. We are dedicated to creating top-of-the-line open-source database solutions and providing support for any issues that may arise. We encourage feedback and bug reports to help us continually improve.&lt;/p>
&lt;p>We stay updated on &lt;a href="https://perconadev.atlassian.net/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> through our own platform as well as &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other sources&lt;/a> to ensure we have the most up-to-date information. To make it easier for you, we have compiled a central list of the most critical bugs for your reference in this edition of our bug report.&lt;/p>
&lt;p>In this episode of our bug report, we provide the following list of bugs.&lt;/p>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8983" target="_blank" rel="noopener noreferrer">PS-8983&lt;/a>: System variable &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/group-replication-system-variables.html#sysvar_group_replication_view_change_uuid" target="_blank" rel="noopener noreferrer">group_replication_view_change_uuid&lt;/a> introduced in MySQL 8.0.26 which corrected the issue &lt;a href="https://bugs.mysql.com/bug.php?id=103641" target="_blank" rel="noopener noreferrer">Bug#103641&lt;/a> in where data is inconsistent between nodes after killing primary node in group replication, However there is still an issue where these events are also generated on the standby/secondary cluster in a ClusterSet thus creating errant transactions, and if binlogs containing these events are purged, then it will not be possible to perform a failover between clusters.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.26&lt;/em>&lt;/p>
&lt;p>&lt;em>Fixed Version: 8.0.31-23&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9048" target="_blank" rel="noopener noreferrer">PS-9048&lt;/a>: When innodb_optimize_fulltext_only is enabled and running &lt;code>OPTIMIZE TABLE &lt;table_name>&lt;/code> which has fulltext index actually causing assertion in Percona server debug build, Please note issue is specifically happening when PARSER is &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/fulltext-search-ngram.html" target="_blank" rel="noopener noreferrer">ngram&lt;/a>.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 5.7.42, 8.0.34&lt;/em>
&lt;em>Fixed Version: N/A [Fix in Progress]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9018" target="_blank" rel="noopener noreferrer">PS-9018&lt;/a>: When replica has a non-replicated database then during intensive workload from the source where multi-threaded slave applier (MTS) is enabled and log_slave_updates=0 then DDL executed against this non-replicated database completely stalls the replica instance.&lt;/p>
&lt;p>&lt;em>Upstream Bug: &lt;a href="https://bugs.mysql.com/bug.php?id=113727" target="_blank" rel="noopener noreferrer">113727&lt;/a>&lt;/em>&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.19+&lt;/em>
&lt;em>Fixed Version: N/A [Fix in Review]&lt;/em>
&lt;em>Workaround : Use log_slave_updates=1, Please note enabling this may produce huge binlog volume on the replica which may or may not be feasible with respect to storage.&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9083" target="_blank" rel="noopener noreferrer">PS-9083&lt;/a>: Percona server crashes when server is running with slow_query_log in conjunction with &lt;em>long_query_time&lt;/em>, &lt;em>log_slow_verbosity = profiling&lt;/em>,&lt;em>query_info&lt;/em> variables.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.35&lt;/em>
&lt;em>Fixed Version: 8.0.36 [Pending Release]&lt;/em>
&lt;em>Workaround : Remove “query_info” from log_slow_verbosity&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9081" target="_blank" rel="noopener noreferrer">PS-9081&lt;/a>: Materializing happens when a query is being executed against performance_schema.data_locks can lead to excessive memory usage and OOM.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.34+&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PS 8.0.37&lt;/em>
&lt;em>Workaround : Putting a LIMIT clause to read queries.&lt;/em>&lt;/p>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4341" target="_blank" rel="noopener noreferrer">PXC-4341&lt;/a>: Execution of prepared statement after FLUSH TABLES makes the node abort from the cluster.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33+&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PXC 8.0.36&lt;/em>
&lt;em>Workaround : There is no straight forward workaround but one can run the prepared statement and FLUSH TABLES statement separately.&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4316" target="_blank" rel="noopener noreferrer">PXC-4316&lt;/a>: Network loss may lead to node’s logs flooded with “changed identity” events which eventually let primary node go non-primary, and reconnect another node. It will keep non primary nodes so we ended with all nodes as non primary.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33+&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PXC 8.0.36&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4348" target="_blank" rel="noopener noreferrer">PXC-4348&lt;/a>: Cluster state interrupted with MDL BF-BF conflict when forcing deadlock. To hit the crash we are required to run queries on multiple sessions where one session should run “optimize table &lt;tbl_name>;” multiple times so mysqlslap is the right candidate to repeat this behavior and other sessions will run delete/insert on the same table.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33+&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PXC 8.0.36&lt;/em>&lt;/p>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2217" target="_blank" rel="noopener noreferrer">PT-2217&lt;/a>: When running pt-mongodb-summary against psmdb6.0/psmdb7.0 it gives error “BSON field ‘getCmdLineOpts.recordStats’ is an unknown field” Please note that PT tool does not work with MongoDB 6.0+.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.5.X&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PT 3.6.0&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2309" target="_blank" rel="noopener noreferrer">PT-2309&lt;/a>: When the primary key is a UUID binary 16 column pt-table-sync hits with error “Cannot nibble table &lt;code>db_name&lt;/code>.&lt;code>table_name&lt;/code> because MySQL chose no index instead of the &lt;code>PRIMARY&lt;/code>”&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.5.7&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PT 3.5.8&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2305" target="_blank" rel="noopener noreferrer">PT-2305&lt;/a>: pt-online-schema-change should error out if server is a slave/replica in row based replication. This can lead to source/replica becoming inconsistent if there are writes on source when the tool runs on replica.&lt;/p>
&lt;p>Please find the example below where data loss is seen:&lt;/p>
&lt;ol>
&lt;li>Set-up classic source-replica&lt;/li>
&lt;li>Make sure &lt;code>binlog_format=row&lt;/code>&lt;/li>
&lt;li>Create a table on master and add sufficient data so that pt-osc takes a little bit of time to run.&lt;/li>
&lt;li>Then start pt-osc on slave, and execute updates/deletes on master.&lt;/li>
&lt;li>Once pt-osc is done, check table checksum or table count to verify the data differences. Please check the below output with row differences:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">master [localhost:22536] {msandbox} (test) > select count(*) from sbtest1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| count(*) |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 999999 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.58 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave1 [localhost:22537] {msandbox} (test) > select count(*) from sbtest1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| count(*) |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1000000 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.40 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>Reported Affected Version/s: 3.5.7&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PT 3.6.0&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2284" target="_blank" rel="noopener noreferrer">PT-2284&lt;/a>: When running pt-kill with the –daemonize option, if the query has character like ‘柏木’, pt-kill process exists with message “Wide character in printf at /usr/bin/pt-kill line 7508.”&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.5.7&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PT 3.6.0&lt;/em>
&lt;em>Workaround : Running pt-kill without –daemonize option manually.&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2089" target="_blank" rel="noopener noreferrer">PT-2089&lt;/a>: When SHOW ENGINE INNODB STATUS reports garbled UTF characters then pt-deadlock-logger crashes with “server ts thread txn_id txn_time user hostname ip db tbl idx lock_type lock_mode wait_hold victim query”&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.3.1, 3.5.7&lt;/em>&lt;/p>
&lt;h2 id="percona-monitoring-and-management-pmm">Percona Monitoring and Management (PMM)&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12806" target="_blank" rel="noopener noreferrer">PMM-12806&lt;/a>: We can’t tune VictoriaMetrics running inside PMM since PMM does not honor the environment variables for VictoriaMetrics. So PMM pre-defines certain flags that allow users to set all other &lt;a href="https://docs.victoriametrics.com/#list-of-command-line-flags" target="_blank" rel="noopener noreferrer">VictoriaMetrics parameters&lt;/a> as environment variables.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Example:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To set downsampling, use the downsampling.period parameter as follows:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e VM_downsampling_period=20d:10m,120d:2h&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This instructs VictoriaMetrics to &lt;a href="https://docs.victoriametrics.com/#deduplication" target="_blank" rel="noopener noreferrer">deduplicate&lt;/a> samples older than 20 days with 10 minute intervals and samples older than 120 days with two hour intervals.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2,40.1&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PMM 2.41.2&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12805" target="_blank" rel="noopener noreferrer">PMM-12805&lt;/a>: When monitoring MongoDB servers, logs might get filled with the a CommandNotSupportOnView message, As a result, disk space fills up.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2,40.0, 2.41.0&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PMM 2.41.2&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12809" target="_blank" rel="noopener noreferrer">PMM-12809&lt;/a>: Common Vulnerabilities and Exposures (CVE) found in PMM gRPC(Remote Procedure Call (RPC)) which impacts PMM v2.40.1+&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.41.0+&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PMM 2.41.2&lt;/em>&lt;/p>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3024" target="_blank" rel="noopener noreferrer">PXB-3024&lt;/a>: Backups are not reliable when running on a secondary node of Group Replication(GR) since –lock-ddl does not have any effect on secondary node of GR.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.28-20, 8.0.31-24&lt;/em>
&lt;em>Fixed Version: 8.0.32-26&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-2928" target="_blank" rel="noopener noreferrer">PXB-2928&lt;/a>: Xtrabackup crashes with signal 11 when taking a backup using &lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/page-tracking.html#install-the-component" target="_blank" rel="noopener noreferrer">–page-tracking&lt;/a> option. So if you are using this option while taking backup then upgrading to PXB 8.0.31 is recommended since there is no workaround available to this issue at the moment.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.29-22&lt;/em>
&lt;em>Fixed Version: 8.0.31-24&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3037" target="_blank" rel="noopener noreferrer">PXB-3037&lt;/a>: In order to assure a consistent replication state, &lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/make-backup-in-replication-env.html?h=safe+backup#the-safe-slave-backup-option" target="_blank" rel="noopener noreferrer">–safe-slave-backup&lt;/a> option stops the replication SQL thread and waits to start backing up until slave_open_temp_tables in SHOW STATUS is zero. If there are no open temporary tables, the backup will take place, otherwise the SQL thread will be started and stopped until there are no open temporary tables. The backup will fail if slave_open_temp_tables does not become zero after –safe-slave-backup-timeout seconds (defaults to 300 seconds). The replication SQL thread will be restarted when the backup finishes, But due to this bug if backup fails in between then SQL thread is not getting restarted. So restarting the SQL thread manually is required.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.31-24, 8.0.35-30&lt;/em>
&lt;em>Fixed Version: No ETA&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-2733" target="_blank" rel="noopener noreferrer">PXB-2733&lt;/a>: backup-lock-timeout and backup-lock-retry-count do not work.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.4.24, 8.0.27-19, 8.0.35-30&lt;/em>
&lt;em>Fixed Version: No ETA&lt;/em>&lt;/p>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-492" target="_blank" rel="noopener noreferrer">K8SPG-492&lt;/a>: Restore job created by PerconaPGRestore doesn’t inherit .spec.instances[].tolerations since restore Job pod get stuck in pending and causing down time.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.2.0&lt;/em>
&lt;em>Fixed Version: It is expected to be fixed by PG operator 2.4.0&lt;/em>
&lt;em>Workaround: Remove &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/" target="_blank" rel="noopener noreferrer">taint&lt;/a>, wait until the restore container is scheduled and re-add it again. &lt;a href="https://perconadev.atlassian.net/browse/K8SPSMDB-958" target="_blank" rel="noopener noreferrer">K8SPSMDB-958&lt;/a>: PMM fails to monitor mongos due to lack of permission.&lt;/em>&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 1.14.0&lt;/em>
&lt;em>Fixed Version: 1.15.0&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-291" target="_blank" rel="noopener noreferrer">K8SPG-291&lt;/a>: Modifying existing backup schedule does not work with PG operator v1.3.0&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 1.3.0&lt;/em>
&lt;em>Fixed Version: 1.4.0&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-286" target="_blank" rel="noopener noreferrer">K8SPG-286&lt;/a>: When requiring TLS for all connections, PMM client fails to connect with “no pg_hba.conf entry”.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 1.2.0, 1.3.0, 2.0.0&lt;/em>
&lt;em>Fixed Version: 1.4.0&lt;/em>&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://perconadev.atlassian.net" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Forums&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Aaditya Dubey</author><category>Percona</category><category>opensource</category><category>PMM</category><category>Kubernetes</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2024/02/BugReportJanuary2024_hu_1daad131906eaa43.jpg"/><media:content url="https://percona.community/blog/2024/02/BugReportJanuary2024_hu_d0b4df4b2e0a5579.jpg" medium="image"/></item><item><title>Unexpected Stalled Upgrade to MySQL 8.0</title><link>https://percona.community/blog/2024/01/26/unexpected-stalled-upgrade-to-mysql-8-0/</link><guid>https://percona.community/blog/2024/01/26/unexpected-stalled-upgrade-to-mysql-8-0/</guid><pubDate>Fri, 26 Jan 2024 00:00:00 UTC</pubDate><description>A multi-tenant database is a database that serves multiple clients, or tenants, who share the same database schema but have separate data sets. One way to achieve data isolation for each client is to create a separate MySQL database for each tenant.</description><content:encoded>&lt;p>A multi-tenant database is a database that serves multiple clients, or tenants, who share the same database schema but have separate data sets. One way to achieve data isolation for each client is to create a separate MySQL database for each tenant.&lt;/p>
&lt;p>Some advantages of this approach are:&lt;/p>
&lt;ul>
&lt;li>It allows for easy backup and restore of individual tenant data.&lt;/li>
&lt;li>It simplifies the database administration and maintenance tasks, as each database can be managed independently.&lt;/li>
&lt;li>Scaling is easily achieved by adding more database servers and distributing tenant databases across them.&lt;/li>
&lt;/ul>
&lt;p>This approach requires a large number of tables on each server. Combined with the default value of &lt;code>innodb_file_per_table=ON&lt;/code>, this results in a large number of files that affects &lt;a href="https://percona.community/blog/2019/07/23/impact-of-innodb_file_per_table-option-on-crash-recovery-time" target="_blank" rel="noopener noreferrer">crash recovery time&lt;/a> or &lt;a href="https://www.percona.com/blog/using-percona-xtrabackup-mysql-instance-large-number-tables" target="_blank" rel="noopener noreferrer">Percona XtraBackup&lt;/a> execution.&lt;/p>
&lt;p>This blog post describes how to take care of a large number of files when upgrading to MySQL 8.0 in-place.&lt;/p>
&lt;h3 id="version-selection">Version Selection&lt;/h3>
&lt;p>A steady stream of MySQL 8.0 minor releases provides improvements and refactoring of new MySQL 8.0 features. However, some of these releases introduce incompatibilities that require corresponding changes on the application side. Limiting scope of the application-side changes, we chose MySQL 8.0.25 version. This was our first step towards the major version 8.0.&lt;/p>
&lt;h2 id="upgrade-in-place">Upgrade In-Place&lt;/h2>
&lt;p>A new MySQL 8.0 option is the upgrade in-place procedure. According to the &lt;a href="https://docs.percona.com/percona-server/8.0/upgrading-guide.html" target="_blank" rel="noopener noreferrer">upgrading guide&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>An in-place upgrade is performed by using existing data on the server and involves the following actions:&lt;/p>
&lt;ul>
&lt;li>Stopping the MySQL 5.7 server&lt;/li>
&lt;li>Replacing the old binaries with MySQL 8.0 binaries&lt;/li>
&lt;li>Starting the MySQL 8.0 server with the same data files.&lt;/li>
&lt;/ul>
&lt;p>While an in-place upgrade may not be suitable for all environments, especially those environments with many variables to consider, the upgrade should work in most cases.&lt;/p>&lt;/blockquote>
&lt;p>As an exception, in the case of an environment with a large number of tables, the upgrade in-place may get &lt;a href="https://forums.mysql.com/read.php?35,697581" target="_blank" rel="noopener noreferrer">stalled for weeks&lt;/a>.&lt;/p>
&lt;p>Below we describe how to debug and resolve such issue.&lt;/p>
&lt;h3 id="encountering-the-issue">Encountering the Issue&lt;/h3>
&lt;p>In our test environment, we encountered a similar issue. Despite steady CPU usage, the in-place upgrade looks stalled. We monitored the upgrade progress by counting files modified in the last 24 hours. Monitoring revealed low modification rates, like&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">find /var/lib/mysql -name "*.ibd" -mtime -1 | wc -l
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">14887&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>that were decreasing. Although the InnoDB files continued to be modified, the decreasing modification rate was too low to be practical.&lt;/p>
&lt;h3 id="investigating-the-issue">Investigating the Issue&lt;/h3>
&lt;p>To debug this problem we used the Linux &lt;a href="https://percona.community/blog/2020/02/05/finding-mysql-scaling-problems-using-perf" target="_blank" rel="noopener noreferrer">&lt;code>perf&lt;/code>&lt;/a> tool. While the &lt;code>mysqld&lt;/code> process was running during upgrade:&lt;/p>
&lt;ul>
&lt;li>we collected &lt;code>perf&lt;/code> data&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">perf record -F 10 -o mysqld.perf -p $(pidof mysqld) -- sleep 20;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ perf record: Woken up 1 times to write data ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ perf record: Captured and wrote 0.256 MB mysqld.perf (1016 samples) ]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>and produced the &lt;code>perf&lt;/code> report&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">perf report --input mysqld.perf --stdio
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># To display the perf.data header info, please use --header/--header-only options.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Total Lost Samples: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Samples: 1K of event 'cpu-clock'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Event count (approx.): 101600000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Overhead Command Shared Object Symbol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># ........ ....... .................. ................................
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 34.55% mysqld libc-2.17.so [.] __sched_yield
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 14.86% mysqld [kernel.kallsyms] [k] __raw_spin_unlock_irq
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11.32% mysqld [kernel.kallsyms] [k] system_call_after_swapgs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11.32% mysqld mysqld [.] Fil_shard::reserve_open_slot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To find out why &lt;code>mysqld&lt;/code> process stuck in the &lt;code>Fil_shard::reserve_open_slot&lt;/code> call, we checked the &lt;a href="https://github.com/percona/percona-server/blob/Percona-Server-8.0.25-15/storage/innobase/fil/fil0fil.cc#L2125" target="_blank" rel="noopener noreferrer">Percona Server source code&lt;/a> that shows:&lt;/p>
&lt;ul>
&lt;li>the function code&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/** Wait for an empty slot to reserve for opening a file.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@return true on success. */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bool Fil_shard::reserve_open_slot(size_t shard_id) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> size_t expected = EMPTY_OPEN_SLOT;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return s_open_slot.compare_exchange_weak(expected, shard_id);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>and the corresponding comments&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">The data structure (Fil_shard) that keeps track of the tablespace ID to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fil_space_t* mapping are hashed on the tablespace ID. The tablespace name to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fil_space_t* mapping is stored in the same shard. A shard tracks the flushing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">and open state of a file. When we run out open file handles, we use a ticketing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">system to serialize the file open, see Fil_shard::reserve_open_slot() and
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Fil_shard::release_open_slot().&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apparently, the stalled upgrade process hit the open files limit, given the large number of files in our environment.&lt;/p>
&lt;h3 id="resolving-the-issue">Resolving the Issue&lt;/h3>
&lt;p>To prevent the &lt;code>mysqld&lt;/code> upgrade process from running out of open file handles, we followed &lt;a href="https://www.percona.com/blog/using-percona-xtrabackup-mysql-instance-large-number-tables" target="_blank" rel="noopener noreferrer">Percona guidance&lt;/a> for setting open files limit&lt;/p>
&lt;ol>
&lt;li>Counted files as&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">find /var/lib/mysql/ -name "*.ibd" | wc -l
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">324780&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>and added another 1000 to this number for other miscellaneous open file needs.&lt;/p>
&lt;ol start="2">
&lt;li>Increased the &lt;code>innodb_open_files&lt;/code> limit in two places:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>added a corresponding line to configuration file &lt;code>/etc/my.cnf&lt;/code> like&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">innodb_open_files = 325780&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>added a corresponding line to the &lt;code>systemd&lt;/code> configuration file such as &lt;code>/etc/systemd/system/mysqld.service.d/override.conf&lt;/code> like&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[Service]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LimitNOFILE = 325780&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With these adjustments our upgrade completed processing of a terabyte of data in just a few hours. To provide more visibility into the upgrade process we also increased the default level of error log verbosity by adding another line to the &lt;code>/etc/my.cnf&lt;/code> file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">log_error_verbosity = 3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Increased verbosity enabled progress monitoring in the mysql error log during upgrade, like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:09.331924Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.25-15) starting as process 16034
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:09.353871Z 1 [System] [MY-011012] [Server] Starting upgrade of data directory.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:09.353986Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:19.412572Z 1 [Note] [MY-012206] [InnoDB] Found 324780 '.ibd' and 0 undo files
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:19.412757Z 1 [Note] [MY-012207] [InnoDB] Using 17 threads to scan 324780 tablespace files
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:28.764032Z 0 [Note] [MY-012200] [InnoDB] Thread# 0 - Checked 15615/20298 files
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:31.718051Z 0 [Note] [MY-012201] [InnoDB] Checked 20298 files
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:31.718440Z 1 [Note] [MY-012208] [InnoDB] Completed space ID check of 324780 files.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:48.821432Z 1 [Note] [MY-012922] [InnoDB] Waiting for purge to start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:48.878058Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:48.885203Z 1 [Note] [MY-011088] [Server] Data dictionary initializing version '80023'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T00:27:49.187508Z 1 [Note] [MY-010337] [Server] Created Data Dictionary for upgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T01:57:55.312683Z 2 [System] [MY-011003] [Server] Finished populating Data Dictionary tables with data.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T01:59:15.187709Z 5 [System] [MY-013381] [Server] Server upgrade from '50700' to '80025' started.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T03:01:09.880932Z 5 [System] [MY-013381] [Server] Server upgrade from '50700' to '80025' completed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2023-10-28T03:01:13.905459Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.25-15' socket: '/var/lib/mysql/mysql.sock' port: 3306 Percona Server (GPL), Release 15, Revision a558ec2.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="discussion">Discussion&lt;/h3>
&lt;p>While we were contemplating if this is a feature or a bug, MySQL release 8.0.28 refactored the related &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_open_files" target="_blank" rel="noopener noreferrer">&lt;code>innodb_open_files&lt;/code>&lt;/a> code. Further details are provided in the corresponding open source commit &lt;a href="https://github.com/percona/percona-server/commit/b184bd30f94df30a8bf178fc327590c5865d33bc" target="_blank" rel="noopener noreferrer">WL#14591 InnoDB: Make system variable &lt;code>innodb_open_files&lt;/code> dynamic&lt;/a>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">- The `innodb_open_files` system variable can now be set with a dynamic SQL procedure `innodb_set_open_files_limit(N)`. If the new value is too low, an error is returned to client with the minimum value presented. If the value is out of bounds or of incorrect type, it will be reported as error also.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- `Fil_system::set_open_files_limit` was added to allow changes to the global opened files limit. The `Fil_system::m_max_n_open` is atomic now and extracted to a separate class `fil::detail::Open_files_limit`, instantiated as `Fil_system::m_open_files_limit`.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- `Fil_shard::reserve_open_slot`, Fil_shard::release_open_slot and static Fil_shard::s_open_slot were removed. Now we have CAS-based system of assuring the opened files will not exceed the limit set.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Thus, the new MySQL 8.0.28 feature – dynamic &lt;code>innodb_open_files&lt;/code> variable – eliminated the need for open files limit adjustments in preparation for MySQL 8.0 upgrade.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>Lessons learned:&lt;/p>
&lt;ul>
&lt;li>Prepare for MySQL 8.0 upgrade in-place by taking a backup of the data directory.&lt;/li>
&lt;li>Take advantage of the Percona Server open source code.&lt;/li>
&lt;li>Follow guidance and advice posted in Percona blogs.&lt;/li>
&lt;/ul>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. Percona has not edited or tested the technical content. Views expressed are the authors’ own. When using the advice from this or any other online resource test ideas before applying them to your production systems, and always secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Alexandre Vaniachine</author><category>Intermediate Level</category><category>MySQL</category><category>Percona Server for MySQL</category><category>upgrade</category><media:thumbnail url="https://percona.community/blog/2024/01/unexpected-stalled-upgrade-to-mysql-8-0_hu_bbbfb5f7838bc57d.jpg"/><media:content url="https://percona.community/blog/2024/01/unexpected-stalled-upgrade-to-mysql-8-0_hu_b802951e238a2edd.jpg" medium="image"/></item><item><title>Our Top Picks from the Kubernetes 1.29 Release</title><link>https://percona.community/blog/2024/01/12/our-top-picks-from-the-kubernetes-release/</link><guid>https://percona.community/blog/2024/01/12/our-top-picks-from-the-kubernetes-release/</guid><pubDate>Fri, 12 Jan 2024 00:00:00 UTC</pubDate><description>The latest Kubernetes version, 1.29, was released on December 13th 2023. Inspired by the Mandala and symbolizing universal perfection, it concludes the 2023 release calendar. This version comes with various exciting improvements, many of which will be helpful for users who run databases on Kubernetes.</description><content:encoded>&lt;p>The latest &lt;strong>Kubernetes&lt;/strong> version, &lt;strong>1.29&lt;/strong>, was released on December 13th 2023. Inspired by the Mandala and symbolizing universal perfection, it concludes the 2023 release calendar. This version comes with various exciting improvements, many of which will be helpful for users who run databases on Kubernetes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="608" height="623" sizes="(max-width: 608px) 100vw, 608px" srcset="https://percona.community/blog/2024/01/k8s-mandala-medium_hu_aa9324d47a8db43a.webp 480w, https://percona.community/blog/2024/01/k8s-mandala-medium_hu_e289a758cceeb1a.webp 608w" src="https://percona.community/blog/2024/01/k8s-mandala-medium_hu_e289a758cceeb1a.webp" alt="Mandala" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Figure 1&lt;/strong> - Mandala created in Excalidraw, not perfectly symmetrical.&lt;/p>
&lt;p>Here, we highlight this latest release’s four key features and improvements. Let’s take a look at them together.&lt;/p>
&lt;h2 id="in-place-update-of-pod-resources">In-Place Update of Pod Resources&lt;/h2>
&lt;p>This alpha feature allows users to change requests and limits for containers without restarting. It simplifies scaling by a lot and opens new opportunities for auto scaling tools like HPA, VP, and Kubernetes Event-driven Autoscaling (KEDA). It removes the barriers of scaling the applications that were not easy to restart.&lt;/p>
&lt;p>When resource resizing is not possible in-place, there are clear strategies for users and controllers (like StatefulSets, JobController, etc.) to handle the situation effectively.&lt;/p>
&lt;p>It was first introduced in 1.27 but moved back to alpha as it requires additional architectural changes. It also has &lt;a href="https://github.com/kubernetes/kubernetes/pull/119665" target="_blank" rel="noopener noreferrer">performance improvements&lt;/a> and comes with &lt;a href="https://github.com/kubernetes/kubernetes/pull/112599" target="_blank" rel="noopener noreferrer">Windows containers support&lt;/a>.
Read more about this in its Kubernetes Enhancement Proposals (&lt;a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources" target="_blank" rel="noopener noreferrer">KEP&lt;/a>) and the &lt;a href="https://github.com/kubernetes/enhancements/issues/1287" target="_blank" rel="noopener noreferrer">issue #1287&lt;/a> created to add this feature.&lt;/p>
&lt;h2 id="kubernetes-volumeattributesclass-modifyvolume">Kubernetes VolumeAttributesClass ModifyVolume&lt;/h2>
&lt;p>The Kubernetes v1.29 release introduces an alpha feature enabling modification of volume attributes, like IOPS and throughput, by altering the volumeAttributesClassName in a PersistentVolumeClaim (PVC). This simplifies volume management by allowing direct updates within Kubernetes, avoiding the need for external provider API management. Previously, users had to create a new StorageClass resource and migrate to a new PVC; now, changes can be made directly in the existing PVC.&lt;/p>
&lt;p>Discover further details in the &lt;a href="https://github.com/kubernetes/enhancements/pull/3780" target="_blank" rel="noopener noreferrer">KEP&lt;/a> and issue &lt;a href="https://github.com/kubernetes/enhancements/issues/3751" target="_blank" rel="noopener noreferrer">#1287&lt;/a>, which was established for the inclusion of this feature.&lt;/p>
&lt;h2 id="readwriteoncepod-persistentvolume-access-mode">ReadWriteOncePod PersistentVolume Access Mode&lt;/h2>
&lt;p>Kubernetes offers access modes for Persistent Volumes (PVs) and Persistent Volume Claims (PVCs), including ReadWriteOnce, ReadOnlyMany, and ReadWriteMany. In particular, ReadWriteOnce restricts volume access to a single node, enabling multiple pods on that node to read from and write to the same volume concurrently. This setup ensures exclusive volume access on a per-node basis while allowing shared volume usage within the node. However, this introduces a potential issue, especially for applications that require exclusive access by a single pod.
In this release, the ReadWriteOncePod access mode for PersistentVolumeClaims has become stable. Now that it is stable, a PVC can be configured to be mounted by a single Pod exclusively.&lt;/p>
&lt;p>Here are the Kubernetes Enhancement Proposal (&lt;a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/2485-read-write-once-pod-pv-access-mode" target="_blank" rel="noopener noreferrer">KEP&lt;/a>) and issue &lt;a href="https://github.com/kubernetes/enhancements/issues/2485" target="_blank" rel="noopener noreferrer">#2485&lt;/a> that led to the development of this feature.&lt;/p>
&lt;h2 id="make-kubernetes-aware-of-the-loadbalancer-behavior">Make Kubernetes aware of the LoadBalancer behavior&lt;/h2>
&lt;p>&lt;strong>kube-proxy’s&lt;/strong> handling of LoadBalancer Service External IPs is set to change. Traditional methods, such as IPVS and iptables, bind these IPs to nodes, optimizing traffic but causing issues with certain cloud providers and bypassing key Load Balancer features.&lt;/p>
&lt;p>There are numerous problems with existing behavior:&lt;/p>
&lt;ul>
&lt;li>Some cloud providers (Scaleway, Tencent Cloud, …) are using the LB’s external IP (or a private IP) as source IP when sending packets to the cluster. This is a problem in the ipvs mode of kube-proxy since the IP is bounded to an interface, and healthchecks from the LB is never coming back.&lt;/li>
&lt;li>Some cloud providers (DigitalOcean, Scaleway, …) have features at the LB level (TLS termination, PROXY protocol, …). Bypassing the LB means missing these features when the packet arrives at the service (leading to protocol errors).&lt;/li>
&lt;/ul>
&lt;p>The solution would be to add a new field in the loadBalancer field of a Service’s status, like ipMode. This new field will be used by kube-proxy in order to not bind the Load Balancer’s External IP to the node (in both IPVS and iptables mode). The value VIP would be the default one (if not set, for instance), keeping the current behavior. The value Proxy would be used to disable the shortcut. This change allows more flexible handling of External IPs, maintaining current behavior as the default and offering an alternative to avoid these issues.&lt;/p>
&lt;p>Read more about this in its Kubernetes Enhancement Proposals (&lt;a href="https://github.com/kubernetes/enhancements/tree/b103a6b0992439f996be4314caf3bf7b75652366/keps/sig-network/1860-kube-proxy-IP-node-binding#kep-1860-make-kubernetes-aware-of-the-loadbalancer-behaviour" target="_blank" rel="noopener noreferrer">KEP&lt;/a>)&lt;/p>
&lt;p>If you are interested in learning about databases on Kubernetes, you can start by &lt;a href="https://www.percona.com/blog/run-mysql-in-kubernetes-solutions-pros-and-cons/" target="_blank" rel="noopener noreferrer">running MySQL in Kubernetes&lt;/a>. Explore the solutions, and weigh the pros and cons.
Also, discover our &lt;a href="https://www.percona.com/blog/cloud-native-predictions-for-2024/" target="_blank" rel="noopener noreferrer">predictions for Cloud Native&lt;/a> technologies for this year.&lt;/p></content:encoded><author>Sergey Pronin</author><author>Edith Puclla</author><category>edith_puclla</category><category>sergey_pronin</category><category>kubernetes</category><category>release</category><media:thumbnail url="https://percona.community/blog/2024/01/k8s-mandala-medium_hu_ccf52cc202bf7b2b.jpg"/><media:content url="https://percona.community/blog/2024/01/k8s-mandala-medium_hu_18b7d20759a07139.jpg" medium="image"/></item><item><title>Data on Kubernetes Community initiatives: Automated storage scaling</title><link>https://percona.community/blog/2024/01/10/data-on-kubernetes-community-initiatives/</link><guid>https://percona.community/blog/2024/01/10/data-on-kubernetes-community-initiatives/</guid><pubDate>Wed, 10 Jan 2024 00:00:00 UTC</pubDate><description>In the world of Kubernetes, where everything evolves quickly. Automated storage scaling stands out as a critical challenge. Members of the Data on Kubernetes Community have proposed a solution to address this issue for Kubernetes operators.</description><content:encoded>&lt;p>In the world of Kubernetes, where everything evolves quickly. Automated storage scaling stands out as a critical challenge. Members of the &lt;a href="https://dok.community/" target="_blank" rel="noopener noreferrer">Data on Kubernetes Community&lt;/a> have proposed a solution to address this issue for Kubernetes operators.&lt;/p>
&lt;p>If, like me, this is your first time hearing about Automated storage scaling, this will help you understand it better:&lt;/p>
&lt;p>&lt;strong>Storage scaling in Kubernetes Operators&lt;/strong> refers to the ability of an application running on Kubernetes to adjust its storage capacity automatically based on demand. In other words, it is about ensuring that an application has the right amount of storage available at any given time, optimizing for performance, cost, and operational efficiency, and doing this as automatically as possible.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4604" height="1773" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2024/01/dok-initiatives_hu_970664ba730cf0cf.webp 768w, https://percona.community/blog/2024/01/dok-initiatives_hu_11c9d4be807e7fde.webp 480w, https://percona.community/blog/2024/01/dok-initiatives_hu_ae3c4a268b94dfb6.webp 1400w" src="https://percona.community/blog/2024/01/dok-initiatives_hu_ae3c4a268b94dfb6.webp" alt="DoKC Initiatives" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As databases grow increasingly integral, the absence of unified solutions for storage scaling is becoming more evident. Let’s explore some existing solutions and their limitations:&lt;/p>
&lt;h2 id="pvc-autoresizer">pvc-autoresizer&lt;/h2>
&lt;p>This project detects and scales &lt;strong>PersistentVolumeClaims&lt;/strong> (PVCs) when the free amount of storage is below the threshold. &lt;a href="https://github.com/topolvm/pvc-autoresizer" target="_blank" rel="noopener noreferrer">pvc-autoresizer&lt;/a> It is and active open source project on GitHub.&lt;/p>
&lt;p>There are certain downsides:&lt;/p>
&lt;ol>
&lt;li>Works with PVCs only. It does not work with StatefulSet and does not have integration with Kubernetes Operator.&lt;/li>
&lt;li>It requires Prometheus stack to be deployed.&lt;/li>
&lt;/ol>
&lt;p>Percona wrote a &lt;a href="https://www.percona.com/blog/storage-autoscaling-with-percona-operator-for-mongodb/" target="_blank" rel="noopener noreferrer">blog post&lt;/a> about pvc-autoresizer to automate storage scaling for MongoDB clusters on Kubernetes.&lt;/p>
&lt;h2 id="ebs-params-controller">EBS params controller&lt;/h2>
&lt;p>This controller provides a way to control IOPS and throughput parameters for EBS volumes provisioned by EBS CSI Driver with annotations on corresponding PersistentVolumeClaim objects in Kubernetes. It also sets some annotations on PVCs backed by EBS CSI Driver representing current parameters and last modification status and timestamps.&lt;/p>
&lt;p>Find more about &lt;a href="https://github.com/Altinity/ebs-params-controller" target="_blank" rel="noopener noreferrer">EBS params controller on GitHub&lt;/a>.&lt;/p>
&lt;h2 id="kubernetes-volume-autoscaler">Kubernetes Volume Autoscaler&lt;/h2>
&lt;p>This automatically increases the size of a Persistent Volume Claim (PVC) in Kubernetes when it is nearing full (either on space OR inode usage). It is a similar solution to pvc-autoresizer. Check out more about &lt;a href="https://github.com/DevOps-Nirvana/Kubernetes-Volume-Autoscaler" target="_blank" rel="noopener noreferrer">Kubernetes Volume Autoscaler&lt;/a>&lt;/p>
&lt;h2 id="kubernetes-event-driven-autoscalingkeda">Kubernetes Event-driven Autoscaling(KEDA)&lt;/h2>
&lt;p>&lt;a href="https://keda.sh/" target="_blank" rel="noopener noreferrer">KEDA&lt;/a> performs horizontal scaling for various resources in k8s, including custom resources. The metric tracking component is already figured out, but unfortunately, it does not work with vertical scaling or storage scaling yet. We opened &lt;a href="https://github.com/kedacore/keda/issues/5232" target="_blank" rel="noopener noreferrer">an issue in GitHub&lt;/a> to start the discussion.&lt;/p>
&lt;p>As you can see, there are some limitations to performing Automated storage scaling, and to address this gap, the &lt;strong>Data on Kubernetes community&lt;/strong> wants to develop a solution that solves practical problems and contributes to the open source community.&lt;/p>
&lt;p>We’re tackling the significant challenge of unexpected disk usage alerts and potential system shutdowns due to insufficient volume space, a common issue in Kubernetes-based databases.&lt;/p>
&lt;h2 id="possible-solutions">Possible Solutions&lt;/h2>
&lt;p>The following possible solutions were proposed:&lt;/p>
&lt;ol>
&lt;li>Operators must be capable of changing the storage size when Custom Resource is changed.&lt;/li>
&lt;li>Operators must create resources following certain standards, like applying annotations with indications of which fields should be changed&lt;/li>
&lt;li>3rd party component (Scaler) will take care of monitoring the storage consumption and changing the field in the CR of the DB&lt;/li>
&lt;/ol>
&lt;p>Our goal as a community is to develop a fully automated solution to prevent these inconveniences and failures.&lt;/p>
&lt;h2 id="final-thoughts">Final Thoughts&lt;/h2>
&lt;p>Once a new solution is validated and proven functional, it will benefit many communities, enabling them to integrate it with their operators. Additionally, it will present an excellent opportunity for Percona to incorporate it into our Operators, enhancing efficiency and facilitating automated storage scaling.&lt;/p>
&lt;p>We invite those interested, especially in this particular project, to join us. This is an opportunity to be at the forefront of shaping the automated scaling solutions in Kubernetes. You can join the &lt;a href="https://join.slack.com/t/dokcommunity/shared_invite/zt-2a0ahuhsh-MdZ4OpF4nr_s4kyOwTurVw" target="_blank" rel="noopener noreferrer">Data on Kubernetes community&lt;/a> on Slack, specifically on the #SIG-Operator.&lt;/p>
&lt;p>Are you interested in understanding Storage Autoscaling in databases? Explore our detailed example of &lt;a href="https://www.percona.com/blog/storage-autoscaling-with-percona-operator-for-mongodb/" target="_blank" rel="noopener noreferrer">Storage Autoscaling using the Percona Operator for MongoDB&lt;/a>. For questions or discussions, feel free to join our experts on the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a>&lt;/p></content:encoded><author>Sergey Pronin</author><author>Edith Puclla</author><category>edith_puclla</category><category>sergey_pronin</category><category>kubernetes</category><category>dok</category><category>storage</category><category>operators</category><media:thumbnail url="https://percona.community/blog/2024/01/dok-initiatives_hu_9ee5c4ee06283257.jpg"/><media:content url="https://percona.community/blog/2024/01/dok-initiatives_hu_5d481efe00c9cbde.jpg" medium="image"/></item><item><title>Volunteering as a Program Committee Member for Data on Kubernetes Day Europe 2024</title><link>https://percona.community/blog/2024/01/10/volunteering-program-committee-data-kubernetes-europe/</link><guid>https://percona.community/blog/2024/01/10/volunteering-program-committee-data-kubernetes-europe/</guid><pubDate>Wed, 10 Jan 2024 00:00:00 UTC</pubDate><description>The Data on Kubernetes Day Europe 2024 Program Committee is a group of professionals and experts responsible for organizing the Data on Kubernetes Day Europe 2024 content for the upcoming co-located events at Kubecon in Paris on 19 March.</description><content:encoded>&lt;p>The Data on &lt;strong>Kubernetes Day Europe 2024 Program Committee&lt;/strong> is a group of professionals and experts responsible for organizing the &lt;strong>Data on Kubernetes Day Europe 2024&lt;/strong> content for the upcoming co-located events at &lt;a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/" target="_blank" rel="noopener noreferrer">Kubecon in Paris&lt;/a> on 19 March.&lt;/p>
&lt;p>As Data on Kubernetes community members, &lt;a href="https://www.linkedin.com/in/sergeypronin/" target="_blank" rel="noopener noreferrer">Sergey Pronin&lt;/a> (Group Product Manager at @Percona) and I (Tech Evangelist) volunteered to evaluate proposal topics submitted for the event through the Sessionize platform. Not only us but also many other members of the Data on Kubernetes community participated as volunteers.&lt;/p>
&lt;p>Community members who participate in this &lt;strong>Program Committee&lt;/strong> evaluate proposals for talks, workshops, and other sessions submitted by potential speakers. This involves identifying each submission’s relevance, quality, and originality and being completely transparent and honest when reviewing a set of talks for the Data On Kubernetes Community co-located event.&lt;/p>
&lt;p>Being a program committee means adhering to guidelines and following the Linux Foundation’s code of conduct.&lt;/p>
&lt;ul>
&lt;li>Be professional and courteous.&lt;/li>
&lt;li>Even express feedback constructively, not destructively.&lt;/li>
&lt;li>Be considerate when choosing communication channels&lt;/li>
&lt;/ul>
&lt;p>After all program committee members completed their evaluations, the Data on Kubernetes Day Co-located Events Europe 2024 &lt;a href="https://colocatedeventseu2024.sched.com/overview/type/Data+on+Kubernetes+Day?iframe=no" target="_blank" rel="noopener noreferrer">schedule&lt;/a> was announced.&lt;/p>
&lt;p>Look at this promising agenda:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="788" height="794" sizes="(max-width: 788px) 100vw, 788px" srcset="https://percona.community/blog/2024/02/dok2_hu_d6db8bc56ec57122.webp 768w, https://percona.community/blog/2024/02/dok2_hu_df4847760ccc2dc.webp 480w, https://percona.community/blog/2024/02/dok2_hu_9f2c59cdf86685f7.webp 788w" src="https://percona.community/blog/2024/02/dok2_hu_9f2c59cdf86685f7.webp" alt="DoKC agenda" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We feel great to be a part of these efforts and to contribute to something significant by making it a reality at an in-person event. Thanks to the &lt;strong>Linux Foundation&lt;/strong> for recognizing our efforts as Program Committee Members and for considering &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a>, an active member of the DoK community.&lt;/p>
&lt;p>In recognition of this support, we earned a badge from The Linux Foundation.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="415" height="428" sizes="(max-width: 415px) 100vw, 415px" srcset="https://percona.community/blog/2024/02/dok3_hu_babaaf238c065e18.webp 415w" src="https://percona.community/blog/2024/02/dok3_hu_babaaf238c065e18.webp" alt="DoKC badge" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you want to know more initiatives &lt;strong>Percona&lt;/strong> have in the DoK community, read &lt;a href="https://percona.community/blog/2024/01/10/data-on-kubernetes-community-initiatives/" target="_blank" rel="noopener noreferrer">Data on Kubernetes Community initiatives: Automated storage scaling&lt;/a>.&lt;/p>
&lt;p>If you have any questions, remember to visit our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>edith_puclla</category><category>kubernetes</category><category>dok</category><category>kubecon</category><category>europe</category><media:thumbnail url="https://percona.community/blog/2024/02/dok3_hu_aaf69425b29d2d1a.jpg"/><media:content url="https://percona.community/blog/2024/02/dok3_hu_d26ec19e4b6de0df.jpg" medium="image"/></item><item><title>Percona Bug Report: November 2023</title><link>https://percona.community/blog/2023/12/19/percona-bug-report-november-2023/</link><guid>https://percona.community/blog/2023/12/19/percona-bug-report-november-2023/</guid><pubDate>Tue, 19 Dec 2023 00:00:00 UTC</pubDate><description>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.</description><content:encoded>&lt;p>At Percona, we operate on the premise that full transparency makes a product better. We strive to build the best open-source database products, but also to help you manage any issues that arise in any of the databases that we support. And, in true open-source form, report back on any issues or bugs you might encounter along the way.&lt;/p>
&lt;p>We constantly update our &lt;a href="https://perconadev.atlassian.net/" target="_blank" rel="noopener noreferrer">bug reports&lt;/a> and monitor &lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">other boards&lt;/a> to ensure we have the latest information, but we wanted to make it a little easier for you to keep track of the most critical ones. This post is a central place to get information on the most noteworthy open and recently resolved bugs.&lt;/p>
&lt;p>In this edition of our bug report, we have the following list of bugs,&lt;/p>
&lt;h2 id="percona-servermysql-bugs">Percona Server/MySQL Bugs&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8086" target="_blank" rel="noopener noreferrer">PS-8086&lt;/a> : Increased memory usage in LRU manager with ROW_FORMAT=COMPRESSED, so it seems that after evicting uncompressed frames for a compressed table, Percona Server LRU manager uses more memory to track them than upstream MySQL.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 5.7.x, 8.0.26, 8.0.32&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8737" target="_blank" rel="noopener noreferrer">PS-8737&lt;/a> / [&lt;a href="https://bugs.mysql.com/bug.php?id=110706" target="_blank" rel="noopener noreferrer">Bug #110706&lt;/a>] : Data will be lost when you perform table rebuild immediately after INSERT or DELETE commands. This means that all ALTER TABLE operations that require table rebuild, including a “null” alteration; that is, an ALTER TABLE statement that “changes” the table to use the storage engine that it already has Eg: “ALTER TABLE t1 ENGINE = InnoDB;”, So after INSERT and DELETE, please do not execute table rebuild statement immediately.You can find the full list of ALTER operations that require table rebuild at &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html&lt;/a> Check for column “Rebuilds Table”. All operations for which this column contains “Yes” or “No*” are affected.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.[28/29/30/31/32]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8428" target="_blank" rel="noopener noreferrer">PS-8428&lt;/a> : ALTER TABLE t ADD FULLTEXT crashes the server when –innodb_encrypt_online_alter_logs=ON. The problem has nothing to do with either innodb_encrypt_online_alter_logs, or Parallel Threads for Online DDL Operations. The issues turned out that in binaries built with OpenSSL 3.0.x my_aes_crypt() function has a flaw and can no longer decrypt data encrypted with the same function previously.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.30-22&lt;/em>&lt;/p>
&lt;p>&lt;em>Fixed version: 8.0.30-22&lt;/em>&lt;/p>
&lt;p>Please don’t get confused with the affected &amp; fix version is the same for this bug since this bug was reported internally during testing of the release build &amp; that’s the reason it gets the same affected &amp; fix version.&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8987" target="_blank" rel="noopener noreferrer">PS-8987&lt;/a> / [&lt;a href="https://bugs.mysql.com/bug.php?id=112935" target="_blank" rel="noopener noreferrer">Bug #112935&lt;/a>] : This bug results in inconsistency seen between MYISAM and MEMORY for simple CREATE and SELECT operation.&lt;/p>
&lt;p>Please check the below scenario where result inconsistency is seen:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Added sample data to MyISAM/InnoDB Tables.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Executed SELECT statement which is a bit complex so can’t be added here but it can be seen in the bug report.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Empty set return when SELECT executed against MyISAM/InnoDB Tables &amp; 4 rows return when same query executed against Memory Engine.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.34-26, 8.0.35-27&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-8990" target="_blank" rel="noopener noreferrer">PS-8990&lt;/a> / [&lt;a href="https://bugs.mysql.com/bug.php?id=112979" target="_blank" rel="noopener noreferrer">Bug #112979&lt;/a>] : MySQL server does not respect system &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_transaction_compression_level_zstd" target="_blank" rel="noopener noreferrer">variable binlog_transaction_compression_level_zstd&lt;/a> so it sets the compression level for binary log transaction compression on this server, which is enabled by the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_transaction_compression" target="_blank" rel="noopener noreferrer">binlog_transaction_compression&lt;/a> system variable. The value is an integer that determines the compression effort, from 1 (the lowest effort) to 22 (the highest effort). If you do not specify this system variable, the compression level is set to 3. As the compression level increases, the data compression ratio increases, which reduces the storage space and network bandwidth required for the transaction payload.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.34-26, 8.0.35-27&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9015" target="_blank" rel="noopener noreferrer">PS-9015&lt;/a> / [&lt;a href="https://bugs.mysql.com/bug.php?id=113256" target="_blank" rel="noopener noreferrer">Bug #113256&lt;/a>] : “DATA_FREE” shows a different value when comparing information_schema.TABLES vs information_schema.PARTITIONS. It is hard to say which result set is correct since we don’t have a source of truth.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 5.7.43-47, 8.0.34-26&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PS-9011" target="_blank" rel="noopener noreferrer">PS-9011&lt;/a> / [&lt;a href="https://bugs.mysql.com/bug.php?id=112946" target="_blank" rel="noopener noreferrer">Bug #112946&lt;/a>] : Prior to 8.0.29 INSTANT column exists on a non-system table with NULL columns in the MySQL SCHEMA which eventually leads to a corruption post 8.0.30+ upgrades. Although it’s probably not a best practice to create tables in mysql SCHEMA, it should not lead to corruption, especially when INSTANT is the default algorithm.&lt;/p>
&lt;h2 id="percona-xtradb-cluster">Percona Xtradb Cluster&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4343" target="_blank" rel="noopener noreferrer">PXC-4343&lt;/a> : Occasionally, during SST, InnoDB tablespace gets silently corrupted, resulting in the later Xtrabackup failure with the following error [MY-012224] [InnoDB] Header page contains inconsistent data in datafile. The triggering condition appears to be PXC 5.7 => 8.0 upgrade, where the corruption manifests in a 2nd node that joins later from the upgraded node. The corruption gets discovered once the 3rd node tries to join from the 2nd node as the donor or when a regular backup is taken from the 2nd node. To workaround this issue, always specify the first upgraded node as a donor.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.34-26&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4237" target="_blank" rel="noopener noreferrer">PXC-4237&lt;/a> : When adding a new node, with PXC tarball installation error is being reported saying &lt;code>[WSREP] Failed to read 'ready &lt;addr>' from: wsrep_sst_xtrabackup-v2&lt;/code>. However, this issue is expected to be fixed by the upcoming release of PXC 8.0.35, and fortunately, below workaround can fix the issue quickly:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Create /var/run/mysqld/ folder owned by mysql OS user.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Backup wsrep_sst_common file before editing:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shell> cp -nvp /usr/local/mysql/bin/wsrep_sst_common /usr/local/mysql/bin/wsrep_sst_common.orig.for-bug-PXC-4237
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Implement the change:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shell> sed -i '297s/.*/set +e; &amp; ; set -e/' /usr/local/mysql/bin/wsrep_sst_common
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Verify the changes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shell> sed -n '297p' /usr/local/mysql/bin/wsrep_sst_common.orig.for-bug-PXC-4237
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MYSQLD_PATH=$(readlink -f /proc/${WSREP_SST_OPT_PARENT}/exe)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shell> sed -n '297p' /usr/local/mysql/bin/wsrep_sst_common
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">set +e; MYSQLD_PATH=$(readlink -f /proc/${WSREP_SST_OPT_PARENT}/exe) ; set -e&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.32-24, 8.0.34-26&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4318" target="_blank" rel="noopener noreferrer">PXC-4318&lt;/a> : PXC cluster stalls and eventually crashes due to a long semaphore wait, which is happening because ha_commit_low does not commit a transaction that does not perform any changes such as an empty transaction and it can’t be controlled since it is an internal process. Fortunately, the upcoming release of PXC 8.0.35 will fix the issue.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33-25&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4336" target="_blank" rel="noopener noreferrer">PXC-4336&lt;/a> : PXC node eviction when a new CHECK CONSTRAINT is created which violates the condition, Eg. table is created with one entry say id 100 and after creation we added CHECK CONSTRAINT using ALTER TABLE t ADD CONSTRAINT CHK_id CHECK (id &lt;=75); Here 100 is not less than 75 which violate the conditions and eventually node become inconsistent and get disconnected/evicted from the cluster. To avoid this scenario make sure to avoid violation of CHECK CONSTRAINT conditions.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.34-26&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXC-4034" target="_blank" rel="noopener noreferrer">PXC-4034&lt;/a> : When PXC cluster uses as a source and an async replication as a replica where “set @@session.sql_log_bin = off;” is used, this introduces a GTID gap in the “gtid_executed” set on the PXC source. As a workaround to the issue avoid using statement with “set @@session.sql_log_bin = off;” in the source/PXC&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 5.7.38-31.59, 8.0.28-19, 8.0.34-26&lt;/em>&lt;/p>
&lt;h2 id="percona-toolkit">Percona Toolkit&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-1724" target="_blank" rel="noopener noreferrer">PT-1724&lt;/a> : Percona toolkit unable to work if user using ‘caching_sha2_password’ Authentication plugin&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.0.13, 3.5.5&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2030" target="_blank" rel="noopener noreferrer">PT-2030&lt;/a> : pt-heartbeat is not compatible with PostgreSQL throwing Cannot get MySQL var character_set_server: DBD::Pg::db selectrow_array failed: ERROR: syntax error at or near “LIKE” LINE 1: SHOW VARIABLES LIKE ‘character_set_server’&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.3.1, 3.5.5&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2083" target="_blank" rel="noopener noreferrer">PT-2083&lt;/a> : when running pt-archiver with –charset option in MySQL 8.0 does not work.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.3.1, 3.5.5&lt;/em>&lt;/p>
&lt;p>&lt;em>Fixed version: 3.5.6 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2106" target="_blank" rel="noopener noreferrer">PT-2106&lt;/a> : In pt-online-schema-change adding column to table (parent table) with having foreign key reference which triggers rebuilding constraints and can cause inconsistencies.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.3.1, 3.5.5&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PT-2207" target="_blank" rel="noopener noreferrer">PT-2207&lt;/a> : pt-archiver doesn’t work when ANSI_QUOTES is set in sql_mode&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 3.5.2, 3.5.5&lt;/em>&lt;/p>
&lt;p>&lt;em>Fixed version: 3.5.6 [Pending Release]&lt;/em>&lt;/p>
&lt;h2 id="percona-monitoring-and-management-pmm">Percona Monitoring and Management (PMM)&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-4712" target="_blank" rel="noopener noreferrer">PMM-4712&lt;/a> : PMM frequently crashes due to out of memory kills with postgres_exporter consuming 20-30GB of RAM and to debug it pprof endpoints to postgres_exporter was missing&lt;/p>
&lt;p>&lt;em>Fixed version: 2.41.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12013" target="_blank" rel="noopener noreferrer">PMM-12013&lt;/a> : rds_exporter unreliable for large deployments which generate gaps in the gathered metrics and some improvement fix done here at PMM-11727&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.35.0&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12349" target="_blank" rel="noopener noreferrer">PMM-12349&lt;/a> : ReplicaSet Summary shows wrong data when a node is gone&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.40.1&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12631" target="_blank" rel="noopener noreferrer">PMM-12631&lt;/a> : Route of /logs.zip crashes with &lt;code>reflect: call of reflect.Value.NumField on string&lt;/code>&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.40.1&lt;/em>
&lt;em>Fixed version: 2.41.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-12738" target="_blank" rel="noopener noreferrer">PMM-12738&lt;/a> : File certificate.conf is required, but not mentioned anywhere in helm charts but in fact, it should not be required.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.40.1&lt;/em>&lt;/p>
&lt;h2 id="percona-xtrabackup">Percona XtraBackup&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-2860" target="_blank" rel="noopener noreferrer">PXB-2860&lt;/a> : Xtrabackup keeps locking table even using –tables-exclude and –lock-ddl-per-table.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33-28&lt;/em>
&lt;em>Fixed version: 8.0.34-29&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3168" target="_blank" rel="noopener noreferrer">PXB-3168&lt;/a> : Under high write load, backup fails with “log block numbers mismatch” error&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33-28, 8.0.34-29&lt;/em>
&lt;em>Fixed version: 8.0.35-30, 8.2&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3079" target="_blank" rel="noopener noreferrer">PXB-3079&lt;/a> : Prepare skips rollback on encrypted tables and completes successfully if the keyring plugin is not loaded.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33-27&lt;/em>
&lt;em>Fixed version: 8.0.34-29&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-3147" target="_blank" rel="noopener noreferrer">PXB-3147&lt;/a> : Xtrabackup failed to execute query ‘DO innodb_redo_log_consumer_register(“PXB”); if sql_mode=’ANSI_QUOTES’ is used.’ This results in the Xtrabackup execution failure.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.33-28&lt;/em>
&lt;em>Fixed version: 8.0.35-30, 8.2&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PXB-2954" target="_blank" rel="noopener noreferrer">PXB-2954&lt;/a> : Xtrabackup failing with “[ERROR] [MY-011825] [Xtrabackup] innodb_init(): Error occurred” to prepare in case of orphan ibd&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 8.0.28-21&lt;/em>
&lt;em>Fixed version: 8.0.32-26&lt;/em>&lt;/p>
&lt;h2 id="percona-kubernetes-operator">Percona Kubernetes Operator&lt;/h2>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-404" target="_blank" rel="noopener noreferrer">K8SPG-404&lt;/a> : Upgrade from percona PostgreSQL Operator 1.3 to 1.4 is ending up with a cluster without any replicas.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 1.4.0&lt;/em>
&lt;em>Fixed version: 1.5.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-420" target="_blank" rel="noopener noreferrer">K8SPG-420&lt;/a> : Ending up in multiple shared repo after cluster pause and unpause.&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 1.4.0&lt;/em>
&lt;em>Fixed version: 1.5.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-435" target="_blank" rel="noopener noreferrer">K8SPG-435&lt;/a> : Pod is recreated when /tmp is filled&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.2.0&lt;/em>
&lt;em>Fixed version: 2.3.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-443" target="_blank" rel="noopener noreferrer">K8SPG-443&lt;/a> : Only english locale is installed, missing other languages support in Postgres&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.2.0&lt;/em>
&lt;em>Fixed version: 2.3.0 [Pending Release]&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/K8SPG-453" target="_blank" rel="noopener noreferrer">K8SPG-453&lt;/a> : pg_stat_monitor hangs primary instance and it’s impossible to disable it&lt;/p>
&lt;p>&lt;em>Reported Affected Version/s: 2.2.0&lt;/em>
&lt;em>Fixed version: 2.3.0 [Pending Release]&lt;/em>&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>We welcome community input and feedback on all our products. If you find a bug or would like to suggest an improvement or a feature, learn how in our post, &lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">How to Report Bugs, Improvements, New Feature Requests for Percona Products&lt;/a>.&lt;/p>
&lt;p>For the most up-to-date information, be sure to follow us on &lt;a href="https://twitter.com/percona" target="_blank" rel="noopener noreferrer">Twitter&lt;/a>, &lt;a href="https://www.linkedin.com/company/percona" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a>, and &lt;a href="https://www.facebook.com/Percona?fref=ts" target="_blank" rel="noopener noreferrer">Facebook&lt;/a>.&lt;/p>
&lt;p>Quick References:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://perconadev.atlassian.net" target="_blank" rel="noopener noreferrer">Percona JIRA&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://bugs.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL Bug Report&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.percona.com/blog/2019/06/12/report-bugs-improvements-new-feature-requests-for-percona-products/" target="_blank" rel="noopener noreferrer">Report a Bug in a Percona Product&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Forums&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Aaditya Dubey</author><category>Opensource</category><category>PMM</category><category>Kubernetes</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2023/12/BugReportNovember2023_hu_9f029881e6ed430d.jpg"/><media:content url="https://percona.community/blog/2023/12/BugReportNovember2023_hu_955695dd594e0750.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.41 preview release</title><link>https://percona.community/blog/2023/12/06/preview-release/</link><guid>https://percona.community/blog/2023/12/06/preview-release/</guid><pubDate>Wed, 06 Dec 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.41 preview release Hello folks! Percona Monitoring and Management (PMM) 2.41 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-241-preview-release">Percona Monitoring and Management 2.41 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.41 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>To see the full list of changes, check out the &lt;a href="https://pmm-release-branch-pr-1182.onrender.com/release-notes/2.41.0.html" target="_blank" rel="noopener noreferrer">PMM 2.41 Release Notes&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker-installation">PMM server Docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Run PMM Server with Docker instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.41.0-rc&lt;/code>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-5997.tar.gz" target="_blank" rel="noopener noreferrer">Download&lt;/a> the latest pmm2-client release candidate tarball for 2.41.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To install pmm2-client package, enable testing repository via Percona-release:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;ol start="3">
&lt;li>Install pmm2-client package for your OS via Package Manager.&lt;/li>
&lt;/ol>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-moitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Run PMM Server as a VM instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.41.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.41.0.ova file&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Run PMM Server hosted at AWS Marketplace instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-0a04085f4c721e913&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us on the [Percona Community Forums](&lt;a href="https://forums.percona.com/]" target="_blank" rel="noopener noreferrer">https://forums.percona.com/]&lt;/a>.&lt;/p></content:encoded><author>Ondrej Patocka</author><category>PMM</category><category>Release</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>The Importance of Anti-Affinity in Kubernetes</title><link>https://percona.community/blog/2023/11/30/anti-affinity-in-kubernetes/</link><guid>https://percona.community/blog/2023/11/30/anti-affinity-in-kubernetes/</guid><pubDate>Thu, 30 Nov 2023 00:00:00 UTC</pubDate><description>Last week, I embarked on the task of deploying our Percona Operator for MongoDB in Kubernetes. After completing the deployment process, I noticed that the status of the Custom Resource Definition for Percona Server for MongoDB was still displaying as ‘initializing’ and two of our Pods remained in a Pending state.</description><content:encoded>&lt;p>Last week, I embarked on the task of deploying our &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a> in Kubernetes. After completing the deployment process, I noticed that the status of the Custom Resource Definition for Percona Server for MongoDB was still displaying as ‘initializing’ and two of our Pods remained in a &lt;strong>Pending&lt;/strong> state.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">edithpuclla@Ediths-MBP % kubectl get perconaservermongodbs.psmdb.percona.com -n mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME ENDPOINT STATUS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db my-db-psmdb-db-mongos.mongodb.svc.cluster.local initializing 4m58s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">edithpuclla@Ediths-MBP % kubectl get pods -n mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-cfg-0 2/2 Running &lt;span class="m">0&lt;/span> 109m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-cfg-1 2/2 Running &lt;span class="m">0&lt;/span> 108m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-cfg-2 0/2 Pending &lt;span class="m">0&lt;/span> 107m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-mongos-0 1/1 Running &lt;span class="m">0&lt;/span> 106m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-mongos-1 1/1 Running &lt;span class="m">0&lt;/span> 106m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-rs0-0 2/2 Running &lt;span class="m">0&lt;/span> 109m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-rs0-1 2/2 Running &lt;span class="m">0&lt;/span> 108m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-db-psmdb-db-rs0-2 0/2 Pending &lt;span class="m">0&lt;/span> 107m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">my-op-psmdb-operator-77b75bbc7c-qd9ls 1/1 Running &lt;span class="m">0&lt;/span> 118m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Upon further inspection of the pod in &lt;strong>pending&lt;/strong> status, I discovered a clear indicator of the error:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl describe pod my-db-psmdb-db-cfg-2 -n mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Events:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Type Reason Age From Message
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ---- ------ ---- ---- -------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Normal NotTriggerScaleUp 3m53s &lt;span class="o">(&lt;/span>x62 over 13m&lt;span class="o">)&lt;/span> cluster-autoscaler pod didn&lt;span class="s1">'t trigger scale-up:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> Warning FailedScheduling 3m27s (x4 over 13m) default-scheduler 0/2 nodes are available: 2 node(s) didn'&lt;/span>t match pod anti-affinity rules. preemption: 0/2 nodes are available: &lt;span class="m">2&lt;/span> No preemption victims found &lt;span class="k">for&lt;/span> incoming pod..&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I took a closer look at the YAML configuration of our CRD in the &lt;strong>Replsets&lt;/strong> section, particularly drawn to the &lt;strong>Affinity&lt;/strong> subsection.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl describe perconaservermongodbs.psmdb.percona.com my-db-psmdb-db -n mongodb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here’s what I discovered:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1002" height="318" sizes="(max-width: 1002px) 100vw, 1002px" srcset="https://percona.community/blog/2023/11/affinity-01_hu_2b17d880a5b6e41f.webp 768w, https://percona.community/blog/2023/11/affinity-01_hu_d0bd1b57b0bf7cfd.webp 480w, https://percona.community/blog/2023/11/affinity-01_hu_15b243336895e6e0.webp 1002w" src="https://percona.community/blog/2023/11/affinity-01_hu_15b243336895e6e0.webp" alt="Affinity" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Affinity&lt;/strong> and &lt;strong>Anti-Affinity&lt;/strong> are key parts of the scheduling process in Kubernetes, and both focus on ensuring that Pods are correctly assigned to Nodes in the cluster. You can configure a Pod to run on a specific node or group of nodes. There are several ways to achieve this, &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector" target="_blank" rel="noopener noreferrer">nodeSelector&lt;/a> is the simplest way to constrain Pods to nodes with specific labels. Affinity and anti-affinity expand the types of constraints you can define and give you more flexibility.&lt;/p>
&lt;p>Let’s explore what it means to specify anti-affinity rules for the ReplicaSets.&lt;/p>
&lt;p>The key &lt;strong>kubernetes.io/hostname&lt;/strong> is a well-known label in Kubernetes that is automatically assigned to each node in the cluster. It usually holds the value of the node’s hostname.
When used as a topology key in anti-affinity rules, it implies that the rule should consider the hostname of the nodes. In simpler terms, it’s telling Kubernetes to not to schedule the pods of this ReplicaSet on the same physical or virtual host (node).&lt;/p>
&lt;p>If we review our cluster, it has two nodes for installing the Percona Operator for MongoDB.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">edithpuclla@Ediths-MBP ~ % kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-mongo-operator-test-default-pool-7c118de9-b9vc Ready &lt;none> 68m v1.27.4-gke.900
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-mongo-operator-test-default-pool-7c118de9-ts16 Ready &lt;none> 68m v1.27.4-gke.900&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In the context of databases like MongoDB, High availability is often achieved through replication, ensuring that the database can continue to operate even if one or more nodes fail. Within a MongoDB Replica Set, there are multiple copies of the data, and these copies are hosted on different Replica Set members. The default HA MongoDB topology is a 3-member Replica Set. &lt;strong>Percona Operator for MongoDB&lt;/strong> deploys MongoDB in the same topology by default. With anti-affinity set to kubernetes.io/hostname, that means at least 3 Kubernetes worker nodes are needed to deploy MongoDB.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2136" height="1528" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/11/affinity-02_hu_da1de3137957f67.webp 768w, https://percona.community/blog/2023/11/affinity-02_hu_5474a5525cfb8d25.webp 480w, https://percona.community/blog/2023/11/affinity-02_hu_6e4801abc77d6f69.webp 1400w" src="https://percona.community/blog/2023/11/affinity-02_hu_6e4801abc77d6f69.webp" alt="Affinity" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We created the minimum three nodes that the &lt;strong>Percona Operator for MongoDB&lt;/strong> needs. We see that we don’t have the error with Antiaffinity in the Pods because each Pod was located appropriately in different nodes. Now our operator and our database were deployed correctly.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">edithpuclla@Ediths-MBP ~ % kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-mongo-operator-test-default-pool-e4e024a8-1dj3 Ready &lt;none> 76s v1.27.4-gke.900
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-mongo-operator-test-default-pool-e4e024a8-d6j2 Ready &lt;none> 74s v1.27.4-gke.900
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gke-mongo-operator-test-default-pool-e4e024a8-jkkr Ready &lt;none> 76s v1.27.4-gke.900&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we list all the resources in our namespace, we can see that all pods are running properly and all the resources have been created.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">edithpuclla@Ediths-MBP ~ % kubectl get all -n mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-cfg-0 2/2 Running &lt;span class="m">0&lt;/span> 4m40s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-cfg-1 2/2 Running &lt;span class="m">0&lt;/span> 4m2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-cfg-2 2/2 Running &lt;span class="m">0&lt;/span> 3m20s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-mongos-0 1/1 Running &lt;span class="m">0&lt;/span> 2m56s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-mongos-1 1/1 Running &lt;span class="m">0&lt;/span> 2m39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-rs0-0 2/2 Running &lt;span class="m">0&lt;/span> 4m39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-rs0-1 2/2 Running &lt;span class="m">0&lt;/span> 3m59s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-db-psmdb-db-rs0-2 2/2 Running &lt;span class="m">0&lt;/span> 3m28s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pod/my-op-psmdb-operator-77b75bbc7c-q2rqh 1/1 Running &lt;span class="m">0&lt;/span> 6m47s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME TYPE CLUSTER-IP EXTERNAL-IP PORT&lt;span class="o">(&lt;/span>S&lt;span class="o">)&lt;/span> AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/my-db-psmdb-db-cfg ClusterIP None &lt;none> 27017/TCP 4m40s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/my-db-psmdb-db-mongos ClusterIP 10.72.17.115 &lt;none> 27017/TCP 2m56s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/my-db-psmdb-db-rs0 ClusterIP None &lt;none> 27017/TCP 4m39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY UP-TO-DATE AVAILABLE AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deployment.apps/my-op-psmdb-operator 1/1 &lt;span class="m">1&lt;/span> &lt;span class="m">1&lt;/span> 6m47s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME DESIRED CURRENT READY AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">replicaset.apps/my-op-psmdb-operator-77b75bbc7c &lt;span class="m">1&lt;/span> &lt;span class="m">1&lt;/span> &lt;span class="m">1&lt;/span> 6m47s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/my-db-psmdb-db-cfg 3/3 4m41s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/my-db-psmdb-db-mongos 2/2 2m58s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/my-db-psmdb-db-rs0 3/3 4m40s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In conclusion, affinity and anti-affinity in Kubernetes are tools for strategically placing pods in a cluster to optimize factors such as performance, availability, and compliance, which are critical for the smooth and efficient operation of containerized applications, also setting up anti-affinity rules like &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/constraints.html#affinity-and-anti-affinity" target="_blank" rel="noopener noreferrer">failure-domain.beta.kubernetes.io/zone&lt;/a> in Kubernetes is a key strategy for keeping clusters running, especially in production environments. This approach spreads pods across different availability zones, which means if one zone has an issue, the others can keep the system running. It’s a smart way to ensure your cluster can handle unexpected outages, making it a popular choice for those who need their Kubernetes setups to be reliable and available at all times.&lt;/p>
&lt;p>Learn more about our &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>, and if you have questions or comments, you can write to us on our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>Kubernetes</category><category>Mongodb</category><media:thumbnail url="https://percona.community/blog/2023/11/affinity-intro_hu_329445018cc2184f.jpg"/><media:content url="https://percona.community/blog/2023/11/affinity-intro_hu_57483ac446857898.jpg" medium="image"/></item><item><title>Day 02: The Kubernetes Application Lifecycle</title><link>https://percona.community/blog/2023/11/20/day-02-the-kubernetes-application-lifecycle/</link><guid>https://percona.community/blog/2023/11/20/day-02-the-kubernetes-application-lifecycle/</guid><pubDate>Mon, 20 Nov 2023 00:00:00 UTC</pubDate><description>If you are in the world of application development, you know that every application has a lifecycle. An application lifecycle refers to the stages that our application goes through from initial planning, building, deployment, monitoring, and maintenance in different environments where our application can be executed.</description><content:encoded>&lt;p>If you are in the world of application development, you know that every application has a lifecycle. An application lifecycle refers to the stages that our application goes through from initial planning, building, deployment, monitoring, and maintenance in different environments where our application can be executed.&lt;/p>
&lt;p>On the other hand, the &lt;strong>Kubernetes Application Lifecycle&lt;/strong> refers exclusively to applications deployed and managed in Kubernetes clusters. This differs from the normal application lifecycle because Kubernetes introduces new principles, practices, and tools for managing applications on containers.&lt;/p>
&lt;p>In this blog post, we will talk about these phases &lt;strong>Day 0&lt;/strong>, &lt;strong>Day 1&lt;/strong> and &lt;strong>Day 2&lt;/strong> in the lifecycle of an
application in Kubernetes and we will focus specifically on the phase of &lt;strong>Day 2&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2271" height="1130" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/11/day2_hu_9788f9b4b2b535a.webp 768w, https://percona.community/blog/2023/11/day2_hu_ebb9ac883c7802c2.webp 480w, https://percona.community/blog/2023/11/day2_hu_cf2948f3209ed3e9.webp 1400w" src="https://percona.community/blog/2023/11/day2_hu_cf2948f3209ed3e9.webp" alt="day02" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Image 1&lt;/strong>: Day 0, Day 1 and Day 2 in the Kubernetes Application Lifecycle&lt;/p>
&lt;p>&lt;strong>Day 0&lt;/strong> refers to the preparation stage before deploying applications in Kubernetes. It’s the stage of identifying goals, planning the infrastructure. Ensuring that the development team has knowledge about Kubernetes and best practices. It’s a stage for investment in training. And the evaluation of the application components to determine which are suitable for use within &lt;strong>containers&lt;/strong> and &lt;strong>Kubernetes&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Day 1&lt;/strong> is the stage that involves deploying the application in Kubernetes clusters and the creation of Kubernetes resources: deployments, pods, services. Additionally, it includes configuration management and the implementation of basic monitoring following the decisions made on &lt;strong>Day 0&lt;/strong>.&lt;/p>
&lt;p>Finally &lt;strong>Day 2&lt;/strong>, our application is already running in Kubernetes clusters by reaching this stage. Day 2 refers to the management, monitoring, and optimization of our Kubernetes clusters over the long term.&lt;/p>
&lt;p>Day 2 involves:&lt;/p>
&lt;ul>
&lt;li>Gathering information from our Kubernetes clusters through monitoring and logging.&lt;/li>
&lt;li>Scaling our application, either horizontally or vertically.&lt;/li>
&lt;li>Application of security best practices and compliance with policies.&lt;/li>
&lt;li>Establishing backups and recovery processes to protect our data and application from future disasters.&lt;/li>
&lt;/ul>
&lt;p>Day 2, activities focus on sustainability, efficiency, and long-term continuous improvement to ensure the stability of our application and meet customer expectations.&lt;/p>
&lt;p>Let’s see how &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a> takes charge of &lt;strong>Day 2&lt;/strong>&lt;/p>
&lt;p>For Percona, a company specializing in the management of open source databases like MySQL, PostgreSQL, MongoDB, Day 2 refers to the ongoing efforts to ensure that database systems are running efficiently securely, and in alignment with business objectives.&lt;/p>
&lt;p>Here are some examples of how Percona handles this phase:&lt;/p>
&lt;p>To achieve Performance Monitoring, if you use our &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona Kubernetes Operators&lt;/a>, you can integrate it with Percona Monitoring and Management (PMM) to check the performance of your databases in real time. Monitor query execution times, resource utilization, and server health. PMM helps to identify bottlenecks and inefficiencies, allowing for timely optimization and tuning.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1133" height="694" sizes="(max-width: 1133px) 100vw, 1133px" srcset="https://percona.community/blog/2023/11/pmm_hu_9d315e8a1484ec08.webp 768w, https://percona.community/blog/2023/11/pmm_hu_5681d001d9887e3c.webp 480w, https://percona.community/blog/2023/11/pmm_hu_598655045eb4fbb5.webp 1133w" src="https://percona.community/blog/2023/11/pmm_hu_598655045eb4fbb5.webp" alt="pmm" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Image 2: this is what the PMM Dashboard interface looks like when monitoring your database resources.&lt;/p>
&lt;p>If we discuss data protection and disaster recovery, using &lt;a href="https://docs.percona.com/percona-xtrabackup/innovation-release/" target="_blank" rel="noopener noreferrer">Percona XtraBackup&lt;/a>, an open-source backup utility for MySQL-based servers. In that case, you can ensure that your database remains fully accessible during scheduled maintenance periods.&lt;/p>
&lt;p>As for scaling strategy and high availability, adopting solutions such as &lt;a href="https://www.percona.com/mysql/software/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster&lt;/a> or &lt;a href="https://www.percona.com/mysql/software/percona-server-for-mysql" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a> enables us to secure the database and efficiently manage increased workloads, all while keeping downtime to a minimum.&lt;/p>
&lt;p>These were just some examples of what &lt;strong>Percona does for Day 2&lt;/strong> to maintain tasks crucial for the business and that relies on databases to keep critical applications and services running.&lt;/p>
&lt;p>​​Are you interested in learning more about Kubernetes or need assistance with your cloud-native strategy? With Percona Kubernetes Operators, you can manage database workloads on any supported Kubernetes cluster running in private, public, hybrid, or multi-cloud environments. They are 100% open source, free from vendor lock-in, usage restrictions, and expensive contracts, and include enterprise-ready features by default. Learn more about &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona Kubernetes Operators&lt;/a>&lt;/p></content:encoded><author>Edith Puclla</author><category>Kubernetes</category><category>Operators</category><category>Percona</category><media:thumbnail url="https://percona.community/blog/2023/11/day2_hu_b9de33838cf601de.jpg"/><media:content url="https://percona.community/blog/2023/11/day2_hu_40f874d434ec4f3d.jpg" medium="image"/></item><item><title>Data On Kubernetes</title><link>https://percona.community/blog/2023/11/10/data-on-kubernetes/</link><guid>https://percona.community/blog/2023/11/10/data-on-kubernetes/</guid><pubDate>Fri, 10 Nov 2023 00:00:00 UTC</pubDate><description>If you’ve attended one of the Kubecon talks or related events, you’ve likely encountered the phrase Data on Kubernetes. To understand what this means, let’s explore some fundamental concepts related to Kubernetes, workload, stateless, and stateful applications.</description><content:encoded>&lt;p>If you’ve attended one of the Kubecon talks or related events, you’ve likely encountered the phrase &lt;strong>Data on Kubernetes&lt;/strong>.
To understand what this means, let’s explore some fundamental concepts related to &lt;strong>Kubernetes&lt;/strong>, &lt;strong>workload&lt;/strong>, &lt;strong>stateless&lt;/strong>, and &lt;strong>stateful&lt;/strong> applications.&lt;/p>
&lt;h2 id="kubernetes-workload-stateless-and-stateful-applications">Kubernetes, workload, stateless and stateful applications&lt;/h2>
&lt;p>&lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a> is a &lt;strong>container orchestration&lt;/strong> tool that has already become an industry standard. When we talk about “container orchestration”, we are referring to the automated management and coordination of containers using Kubernetes.&lt;/p>
&lt;p>Now, let’s explore what a workload is in the context of Kubernetes. A workload represents an application running on Kubernetes. An application may consist of a single component or multiple components working together. These components are packaged into containers operating within a group of &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" target="_blank" rel="noopener noreferrer">Pods&lt;/a>.&lt;/p>
&lt;p>There are two types of workloads, depending on the nature of the application: Stateless and Stateful.&lt;/p>
&lt;p>In a stateless application, the client session data is not stored on the server. This is because the application doesn’t need to retain past interactions to function. However, in a stateful application, storing client session data is essential as it is necessary for subsequent interactions within the application.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2186" height="1253" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/11/steteless-and-stateful_hu_43c468f235f68b2e.webp 768w, https://percona.community/blog/2023/11/steteless-and-stateful_hu_fc979b943385167.webp 480w, https://percona.community/blog/2023/11/steteless-and-stateful_hu_8d2d168b89c925d1.webp 1400w" src="https://percona.community/blog/2023/11/steteless-and-stateful_hu_8d2d168b89c925d1.webp" alt="steteless-and-stateful" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now, we are already familiar with Kubernetes, workloads, stateless and stateful applications, and we also understand that Pods are responsible for managing these types of workloads.&lt;/p>
&lt;h2 id="built-in-workload-resources-in-kubernetes">Built-in Workload Resources in Kubernetes&lt;/h2>
&lt;p>In a Kubernetes cluster, we can have thousands of Pods, and we don’t need to directly manage them individually. Instead, we utilize &lt;a href="https://kubernetes.io/docs/concepts/workloads/" target="_blank" rel="noopener noreferrer">workload resources&lt;/a> to manage a group of Pods and choose what workload resource depends on the type of workload we are dealing with, Stateless or Stateful.&lt;/p>
&lt;p>For example, if we have stateless applications, we can use the &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" target="_blank" rel="noopener noreferrer">Deployment&lt;/a> and &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" target="_blank" rel="noopener noreferrer">ReplicaSet&lt;/a> resources, which are well-suited for this type of workflow. On the other hand, the &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" target="_blank" rel="noopener noreferrer">StatefulSet&lt;/a> resource allows us to run Pods that need to maintain state.&lt;/p>
&lt;p>&lt;em>Data on Kubernetes&lt;/em> refers to the management and storage of data within the Kubernetes ecosystem. Kubernetes provides a robust framework for handling data, making it a versatile platform for both &lt;em>stateless and stateful&lt;/em> applications while ensuring data durability, availability, and security.&lt;/p>
&lt;h2 id="the-challenge">The challenge&lt;/h2>
&lt;p>Kubernetes was initially designed to run stateless applications. However, the number of stateful applications running on Kubernetes has increased significantly. There are many challenges when it comes to running applications with state in Kubernetes, such as data management strategies, volume persistence, and others. According to the &lt;a href="https://dok.community/wp-content/uploads/2021/10/DoK_Report_2021.pdf" target="_blank" rel="noopener noreferrer">2021 Data on Kubernetes report&lt;/a> of more than 500 executives and technology leaders, 90% believe it is ready for stateful workloads, and a large majority (70%) are running them in production, with databases topping the list. This gives rise to initiatives aimed at standardizing the requirements for managing stateful applications on Kubernetes.&lt;/p>
&lt;p>This is how &lt;a href="https://community.cncf.io/data-on-kubernetes/" target="_blank" rel="noopener noreferrer">Data on the Kubernetes Community&lt;/a> emerges. The Data on Kubernetes (DoKc) community was established in spring 2020. It is an openly governed group of curious and experienced practitioners, drawing inspiration from the Cloud Native Computing Foundation (CNCF) and the Apache Software Foundation. They aim to help create and improve techniques for using Kubernetes with data.&lt;/p>
&lt;p>There are several organizations that are part of the Data on Kubernetes community, and Percona is part of it as well. &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a> is adding efforts in DoKC Operator SIG(Special Interest Groups), where we discuss gaps in information around K8s operators for the industry-at-large &amp; co-creates projects to fill the gap. Watch the &lt;a href="https://www.youtube.com/watch?v=TmDdkBPW_hI" target="_blank" rel="noopener noreferrer">Kubernetes Database Operator Landscape&lt;/a> panel discussion to learn more about the community efforts in Data on Kubernetes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="728" height="542" sizes="(max-width: 728px) 100vw, 728px" srcset="https://percona.community/blog/2023/11/dok_hu_14a987fa6ec20d45.webp 480w, https://percona.community/blog/2023/11/dok_hu_eb94691d3d2061c8.webp 728w" src="https://percona.community/blog/2023/11/dok_hu_eb94691d3d2061c8.webp" alt="dok" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Data on Kubernetes is a crucial concept in the Kubernetes ecosystem. Kubernetes was initially designed for the stateless application, now faces the challenge of managing stateful workloads, and is more notable in databases. The Data on Kubernetes (DoKc) community has emerged to address these challenges and standardize the management of stateful applications, drawing inspiration from industry standards like CNCF and Apache Software Foundation.&lt;/p>
&lt;p>If you want to be part of them, you are welcome to join &lt;a href="https://community.cncf.io/data-on-kubernetes/" target="_blank" rel="noopener noreferrer">DoKC&lt;/a>. Also, check this outstanding &lt;a href="https://www.youtube.com/watch?v=TmDdkBPW_hI" target="_blank" rel="noopener noreferrer">Kubernetes Database Operators Landscape&lt;/a>, where members of DoKC talk about operations for data workloads on Kubernetes.&lt;/p></content:encoded><author>Edith Puclla</author><category>Kubernetes</category><category>DoK</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2023/11/steteless-and-stateful_hu_521986dedeb9a949.jpg"/><media:content url="https://percona.community/blog/2023/11/steteless-and-stateful_hu_f4c812c7cccc0443.jpg" medium="image"/></item><item><title>Exploring Kubernetes Operators</title><link>https://percona.community/blog/2023/11/03/kubernetes-operators/</link><guid>https://percona.community/blog/2023/11/03/kubernetes-operators/</guid><pubDate>Fri, 03 Nov 2023 00:00:00 UTC</pubDate><description>The concept of Kubernetes Operators was introduced around 2016 by the CoreOS Linuxdevelopment team. They were in search of a solution to improve automated container management within Kubernetes, primarily with the goal of incorporating operational expertise directly into the software.</description><content:encoded>&lt;p>The concept of &lt;strong>Kubernetes Operators&lt;/strong> was introduced around 2016 by the &lt;a href="https://en.wikipedia.org/wiki/Container_Linux" target="_blank" rel="noopener noreferrer">CoreOS Linux&lt;/a>development team. They were in search of a solution to improve automated container management within Kubernetes, primarily with the goal of incorporating operational expertise directly into the software.&lt;/p>
&lt;p>According to the &lt;a href="https://www.cncf.io/blog/2022/06/15/kubernetes-operators-what-are-they-some-examples/#:~:text=K8s%20Operators%20are%20controllers%20for,Custom%20Resource%20Definitions%20%28CRD%29." target="_blank" rel="noopener noreferrer">Cloud Native Computing Foundation&lt;/a>, &lt;strong>“Operators are software extensions that use custom resources to manage applications and their components”.&lt;/strong>&lt;/p>
&lt;p>Kubernetes is designed for automation, offering essential automation features. It can automatically deploy and manage workloads. The definition provided by CNCF regarding operators, as mentioned above, highlights the flexibility we have to customize the automation capabilities made possible by Kubernetes Operators using custom resources.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2368" height="1585" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/11/k8s-01_hu_f29a047c1b6dee52.webp 768w, https://percona.community/blog/2023/11/k8s-01_hu_a494c739c850a2fe.webp 480w, https://percona.community/blog/2023/11/k8s-01_hu_44e70d10cf39ac9f.webp 1400w" src="https://percona.community/blog/2023/11/k8s-01_hu_44e70d10cf39ac9f.webp" alt="kubernetes-operators" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In Kubernetes, certain applications require manual attention as Kubernetes can not autonomously manage them. This is especially the case with databases. This is where Operators come into play.
Databases are complex entities that also have complex database operations and features that Kubernetes itself may not inherently understand. While deploying a database in Kubernetes manually isn’t a problem, the true strength of operators shines during &lt;a href="https://thenewstack.io/cloud-native-day-2-operations-why-this-begins-on-day-0/" target="_blank" rel="noopener noreferrer">Day 2 operations&lt;/a>, which include tasks such as backups, failover, and scaling. Operators automate these manual tasks for applications within Kubernetes.&lt;/p>
&lt;p>The main challenge that arises when implementing &lt;strong>containerized databases&lt;/strong> is the problem of &lt;strong>data persistence&lt;/strong>. This is the challenge for containers in general, and it is more critical in the context of databases despite ongoing advances in container maturity. Kubernetes operators are designed to address this gap. While it is possible to use Kubernetes resources like Persistent Volume Claims (PVCs) without operators, operators simplify the process by providing a higher level of abstraction and automation.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1038" height="839" sizes="(max-width: 1038px) 100vw, 1038px" srcset="https://percona.community/blog/2023/11/k8s-02_hu_76601a2bbb0aa96a.webp 768w, https://percona.community/blog/2023/11/k8s-02_hu_81f4bfb7e1efb540.webp 480w, https://percona.community/blog/2023/11/k8s-02_hu_9cd86d08bd662c69.webp 1038w" src="https://percona.community/blog/2023/11/k8s-02_hu_9cd86d08bd662c69.webp" alt="kubernetes-operators" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>It is possible to create new operators using the &lt;strong>Kubernetes operator pattern&lt;/strong> concept. This allows you to extend cluster behavior without modifying the Kubernetes code by linking controllers to one or more custom resources. These Operators use and extend the Kubernetes API, a key component within the Kubernetes architecture, with the essential concepts for users to interact with the Kubernetes cluster. They create &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#:~:text=A%20custom%20resource%20is%20an,resources%2C%20making%20Kubernetes%20more%20modular." target="_blank" rel="noopener noreferrer">custom resources&lt;/a> to add new functionality according to the needs of an application to be flexible and scalable. This is how we automate workloads using Kubernetes Operators.&lt;/p>
&lt;p>One of the primary benefits of operators is the &lt;strong>automation&lt;/strong> of repetitive tasks that are often managed by human operators, eliminating errors in application lifecycle management.&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>In this article, we explored an overview of what Kubernetes Operators are; we saw why they are necessary and the benefits of using them. I hope you have gained a general understanding of why Kubernetes Operators are valuable.&lt;/p>
&lt;p>If you want to know more about Kubernetes operators designed specifically for databases, you can visit the &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona website&lt;/a>, where you will find Kubernetes operators created by Percona for &lt;strong>MongoDB&lt;/strong>, &lt;strong>PostgreSQL&lt;/strong>, and &lt;strong>MySQL&lt;/strong>.&lt;/p>
&lt;p>Are you considering creating your own operator? Start by using the &lt;strong>Operator-SDK&lt;/strong>. Additionally, you can watch &lt;a href="https://www.linkedin.com/in/sergeypronin/" target="_blank" rel="noopener noreferrer">Sergey Pronin’s&lt;/a> (Group Product Manager At Percona) talk at the DoK Community about Migrating MongoDB to Kubernetes, where he discusses the reasons why Percona created an Operator for MongoDB.&lt;/p></content:encoded><author>Edith Puclla</author><category>CNCF</category><category>Kubernetes</category><category>Operators</category><category>Databases</category><media:thumbnail url="https://percona.community/blog/2023/11/k8s-01_hu_ed80426dd6e0a7aa.jpg"/><media:content url="https://percona.community/blog/2023/11/k8s-01_hu_88a1c819ea89bc3f.jpg" medium="image"/></item><item><title>Building and Running Percona Everest From Source Code</title><link>https://percona.community/blog/2023/10/30/building-and-running-percona-everest-from-source-code/</link><guid>https://percona.community/blog/2023/10/30/building-and-running-percona-everest-from-source-code/</guid><pubDate>Mon, 30 Oct 2023 00:00:00 UTC</pubDate><description>Digging deeper into the architecture of an open source product</description><content:encoded>&lt;p>&lt;em>Digging deeper into the architecture of an open source product&lt;/em>&lt;/p>
&lt;p>Recently, Percona team &lt;a href="https://www.percona.com/blog/announcing-the-alpha-release-of-percona-everest-an-open-source-private-dbaas/" target="_blank" rel="noopener noreferrer">announced&lt;/a> the public alpha version of a new open source product – Percona Everest. It allows you to create database clusters on Kubernetes cluster.&lt;/p>
&lt;p>I have installed Percona Everest several times and tried its features. Standard installation is very simple and &lt;a href="https://docs.percona.com/everest/quickstart-guide/qs-overview.html" target="_blank" rel="noopener noreferrer">takes a few minutes&lt;/a>.&lt;/p>
&lt;p>But, to understand the product deeper, I came up with the idea to explore repositories, build and run Percona Everest from source.&lt;/p>
&lt;p>In this post, I will explain what I did step by step and what components and frameworks are used in the development of Percona Everest.&lt;/p>
&lt;h2 id="architecture-components-and-tools">Architecture, components, and tools&lt;/h2>
&lt;p>At the top level, we have two components or tools on the user side: Percona Everest App and everestctl CLI tool.&lt;/p>
&lt;p>The Percona Everest App is a basic application that provides a web interface for database creation and management functions. Percona Everest App can be installed on your computer or remote server. The App consists of two major components:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Frontend is a browser-based application providing a web interface for managing clusters and interacting with backend APIs. It is developed with React and TypeScript.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Backend API that process requests from frontend, interact with Kubernetes clusters and databases. It is developed on Golang and PostgreSQL as a database.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>everestctl is a CLI tool for provisioning of Percona Everest on Kubernetes clusters. It is used to install Percona Everest components such as database operators on the Kubernetes cluster. It is developed in Golang and is provided as a ready-made executable, but, in this post, we will also build it from source code.&lt;/p>
&lt;p>Remember that normally, when you install Percona Everest following the instructions in the documentation, the frontend and backend are built and integrated into a single container and run as a single unit.&lt;/p>
&lt;p>Let’s get started with our experiments.&lt;/p>
&lt;h2 id="frontend-installation-and-launch">Frontend installation and launch&lt;/h2>
&lt;p>Percona Everest Frontend is developed using the Bit framework.&lt;/p>
&lt;p>&lt;a href="https://bit.dev/" target="_blank" rel="noopener noreferrer">Bit&lt;/a> is an open source toolchain for the development of composable software using React library and TypeScript.&lt;/p>
&lt;p>Bit is used by about 100K developers, 250+ community plugins and has 16K+ stars on &lt;a href="https://github.com/teambit/bit" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.&lt;/p>
&lt;h3 id="clone-the-frontend-repository">Clone the Frontend repository&lt;/h3>
&lt;p>You need to clone a repository with the Percona Everest frontend:
&lt;a href="https://github.com/percona/percona-everest-frontend" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-everest-frontend&lt;/a>&lt;/p>
&lt;p>&lt;code>git clone git@github.com:percona/percona-everest-frontend.git&lt;/code>&lt;/p>
&lt;h3 id="install-bit">Install Bit&lt;/h3>
&lt;p>Bit requires the npm package manager to install. &lt;a href="https://www.npmjs.com/" target="_blank" rel="noopener noreferrer">npm&lt;/a> is a popular registry of JavaScript packages and libraries. The npm registry contains over 800,000 code packages. Open source developers use npm to share software. Installing npm on your operating system is straightforward. I’m sure you can handle it. It installs along with Node.js.&lt;/p>
&lt;p>Open the Percona Everest Frontend source directory and run the following commands.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">npm i -g @teambit/bvm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bvm install 1.0.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bit install --recurring-install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2258" height="1448" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-frontend_hu_4ab3712868bda73f.webp 768w, https://percona.community/blog/2023/10/everest-frontend_hu_666d8e7b60496e90.webp 480w, https://percona.community/blog/2023/10/everest-frontend_hu_633847c466b597bb.webp 1400w" src="https://percona.community/blog/2023/10/everest-frontend_hu_633847c466b597bb.webp" alt="Percona Everest Frontend" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="launching-the-frontend-application">Launching the frontend application&lt;/h3>
&lt;p>Moving forward, we have two options:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Run the frontend application using Bit.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Build a ready application and copy it to the backend.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>The Percona Everest frontend repository contains versioning branches &lt;code>release-[version]&lt;/code> and the current version in development in the main branch. We will run the latest dev version from main.&lt;/p>
&lt;p>Let’s run the command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bit run everest --skip-watch&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As a result, we can open &lt;code>localhost:3000&lt;/code> in the browser.&lt;/p>
&lt;p>The Frontend is now built, and we can move on to the Backend.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2780" height="1204" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-front-run_hu_fab69f41dce0a613.webp 768w, https://percona.community/blog/2023/10/everest-front-run_hu_24dbb0edae985f16.webp 480w, https://percona.community/blog/2023/10/everest-front-run_hu_5f9106f094417e5e.webp 1400w" src="https://percona.community/blog/2023/10/everest-front-run_hu_5f9106f094417e5e.webp" alt="Percona Everest Frontend Run" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2228" height="1388" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-front-run-result_hu_dee0df6d67ce9b08.webp 768w, https://percona.community/blog/2023/10/everest-front-run-result_hu_818e0782f1a7e1ec.webp 480w, https://percona.community/blog/2023/10/everest-front-run-result_hu_1150e3c3132661ff.webp 1400w" src="https://percona.community/blog/2023/10/everest-front-run-result_hu_1150e3c3132661ff.webp" alt="Percona Everest Frontend Result" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="additional-information">Additional information&lt;/h3>
&lt;p>There is the other way to build a frontend to work with backend. You will need two commands:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bit snap --build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bit artifacts percona.apps/everest --out-dir build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this case, the build will be done to the folder:&lt;/p>
&lt;p>&lt;code>build/percona.apps_everest/artifacts/apps/react-common-js/everest/public/&lt;/code>&lt;/p>
&lt;p>You need to copy all the files to the &lt;code>public/dist&lt;/code> folder of the backend repository. We will talk about backend in the next section.&lt;/p>
&lt;p>The installation process may change over time, so I recommend to keep track of the up-to-date commands in the files:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>README.md&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Makefile&lt;/p>
&lt;/li>
&lt;li>
&lt;p>CI/CD of GitHub configuration, file &lt;code>.github/workflows/ci.yml&lt;/code> in the repository.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="backend">Backend&lt;/h2>
&lt;p>So we’ve launched Frontend, and now it shows an error because it sends requests to the Backend API, and we don’t have it yet.&lt;/p>
&lt;p>We will need to clone the repository with Percona Everest Backend&lt;/p>
&lt;p>&lt;a href="https://github.com/percona/percona-everest-backend" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-everest-backend&lt;/a>&lt;/p>
&lt;p>Percona Everest Backend is developed in Golang using &lt;a href="https://echo.labstack.com/" target="_blank" rel="noopener noreferrer">the Echo framework&lt;/a>. &lt;a href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer">The Echo repository&lt;/a> has over 26k stars on GitHub.&lt;/p>
&lt;p>Generally, it is an API that interacts with the frontend, processing requests and sending them to the Kubernetes cluster.&lt;/p>
&lt;p>Let’s get it up and running.&lt;/p>
&lt;h3 id="run-postgresql-locally">Run PostgreSQL locally&lt;/h3>
&lt;p>You need Docker to run it. I hope you have &lt;a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer">Docker&lt;/a> installed.&lt;/p>
&lt;p>Let’s run one of the two commands in the repository directory:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">make local-env-up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>or&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker-compose up --detach --remove-orphans&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1618" height="386" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/backend-docker-pg_hu_6eba9266cd64b29.webp 768w, https://percona.community/blog/2023/10/backend-docker-pg_hu_cf5df4c5b3bd11e9.webp 480w, https://percona.community/blog/2023/10/backend-docker-pg_hu_71d9ff3f38560565.webp 1400w" src="https://percona.community/blog/2023/10/backend-docker-pg_hu_71d9ff3f38560565.webp" alt="Percona Everest Backend" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2124" height="824" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_1d3c712f827bcf3c.webp 768w, https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_90fc3bd6fa4cb58c.webp 480w, https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_675777ec1f30b096.webp 1400w" src="https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_675777ec1f30b096.webp" alt="Percona Everest Backend" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Using Docker for this process will be replaced by Kubernetes. You can see the YAML manifest in the file:&lt;/p>
&lt;p>&lt;code>/deploy/quickstart-k8s.yaml&lt;/code>&lt;/p>
&lt;h3 id="launch-the-go-app">Launch the Go app&lt;/h3>
&lt;p>We have two options, I use:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">go run cmd/main.go&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But you can also use:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">make run-debug&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Starting with version 0.4.0, you will need to add the SECRETS_ROOT_KEY environment variable before starting the application. The secret key must be used on restarts if you do not start from the scratch.
&lt;code>export SECRETS_ROOT_KEY=$(openssl rand -base64 32)&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1172" height="520" sizes="(max-width: 1172px) 100vw, 1172px" srcset="https://percona.community/blog/2023/10/backend-go-run_hu_20d99c43d6bf7450.webp 768w, https://percona.community/blog/2023/10/backend-go-run_hu_cefc42bada2ef613.webp 480w, https://percona.community/blog/2023/10/backend-go-run_hu_3805eed40fd29d8b.webp 1172w" src="https://percona.community/blog/2023/10/backend-go-run_hu_3805eed40fd29d8b.webp" alt="Percona Everest Backend Go Run" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now we can open the localhost:3000 in the browser again and check that the backend is running. But we see that we have no Kubernetes clusters connected and configured.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2238" height="1406" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/backend-go-run-result_hu_40bce3fa9ecd4d88.webp 768w, https://percona.community/blog/2023/10/backend-go-run-result_hu_14c93208e7039e0.webp 480w, https://percona.community/blog/2023/10/backend-go-run-result_hu_4edcc322ed9bcb9b.webp 1400w" src="https://percona.community/blog/2023/10/backend-go-run-result_hu_4edcc322ed9bcb9b.webp" alt="Percona Everest Backend Go Result" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="everestctl-and-kubernetes-cluster">Everestctl and Kubernetes cluster&lt;/h2>
&lt;p>Another important component of Percona Everest is everestctl. &lt;a href="https://github.com/percona/percona-everest-cli/" target="_blank" rel="noopener noreferrer">everestctl&lt;/a> is a CLI tool responsible for provisioning Percona Everest on Kubernetes clusters.&lt;/p>
&lt;p>We will need:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Kubernetes cluster&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Build and run everestctl&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="preparation-of-the-kubernetes-cluster">Preparation of the Kubernetes Cluster&lt;/h3>
&lt;p>You can use Kubernetes Cluster on AWS, Google Cloud, or minikube.&lt;/p>
&lt;p>The Percona Everest documentation says:&lt;/p>
&lt;p>&lt;em>You must have a publicly accessible Kubernetes cluster to use Percona Everest. EKS or GKE is recommended, as it may be difficult to make it work with local installations of Kubernetes such as minikube, kind, k3d, or similar products.&lt;/em>&lt;/p>
&lt;p>The documentation provides instructions on how to run the test cluster&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://docs.percona.com/everest/quickstart-guide/eks.html" target="_blank" rel="noopener noreferrer">Create Kubernetes cluster on Amazon Elastic Kubernetes Service (EKS)&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.percona.com/everest/quickstart-guide/gke.html" target="_blank" rel="noopener noreferrer">Create Kubernetes cluster on Google Kubernetes Engine (GKE)&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>In this post, we use &lt;a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank" rel="noopener noreferrer">minikube&lt;/a> which is preliminarily installed.&lt;/p>
&lt;p>&lt;a href="https://github.com/percona/percona-everest-backend/blob/main/Makefile" target="_blank" rel="noopener noreferrer">The Makefile&lt;/a> of &lt;a href="https://github.com/percona/percona-everest-backend" target="_blank" rel="noopener noreferrer">the Percona Everest Backend repository&lt;/a> contains the &lt;code>make k8s&lt;/code> command to start the cluster in minikube.&lt;/p>
&lt;p>Let’s open the directory of the backend repository and launch minikube:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">make k8s-macos&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>or&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">make k8s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2334" height="1348" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-minikube_hu_1ab29b7b022acb47.webp 768w, https://percona.community/blog/2023/10/everest-minikube_hu_e24a20f56f16018f.webp 480w, https://percona.community/blog/2023/10/everest-minikube_hu_fd99d65032e0ceae.webp 1400w" src="https://percona.community/blog/2023/10/everest-minikube_hu_fd99d65032e0ceae.webp" alt="Percona Everest Backend Minikube" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As a result, I see in the console this message:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I also see that I have a minikube cluster with three nodes. It is time to install Percona Everest components using everestctl.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">➜ percona-everest-backend git:(main) ✗ kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube Ready control-plane 3m44s v1.26.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube-m02 Ready &lt;none> 3m22s v1.26.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube-m03 Ready &lt;none> 3m3s v1.26.3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="build-everestctl">Build everestctl&lt;/h3>
&lt;p>Let’s clone the repository: &lt;a href="https://github.com/percona/percona-everest-cli/" target="_blank" rel="noopener noreferrer">https://github.com/percona/percona-everest-cli/&lt;/a>&lt;/p>
&lt;p>Open the repository folder and run build:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">make build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>everestctl is built in the binary file &lt;code>/bin/everest&lt;/code>&lt;/p>
&lt;p>Grant execution privileges&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">chmod +x ./bin/everest&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s run the Percona Everest installation:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">./bin/everest install operators&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That command will start the installation wizard.&lt;/p>
&lt;p>I will leave all default values, just pressing Enter. Otherwise, you can experiment with the settings, it is your choice.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2456" height="872" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-wizards_hu_6a97ee380e44a886.webp 768w, https://percona.community/blog/2023/10/everest-wizards_hu_119b4b2325668979.webp 480w, https://percona.community/blog/2023/10/everest-wizards_hu_8546dbe4aab44ce5.webp 1400w" src="https://percona.community/blog/2023/10/everest-wizards_hu_8546dbe4aab44ce5.webp" alt="Percona Everest Wizard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As a result, the following processes will run on the cluster:&lt;/p>
&lt;p>Creating namespace percona-everest.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Installing Operator Lifecycle Manager (OLM).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Installing &lt;a href="https://github.com/percona/everest-catalog" target="_blank" rel="noopener noreferrer">Percona OLM Catalog&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Installing Percona Operators for the databases selected in the wizard.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Installing &lt;a href="https://github.com/percona/everest-operator" target="_blank" rel="noopener noreferrer">everest-operator&lt;/a> operator.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Creating services and roles.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>That’s it! We’ve installed Percona Everest completely. You can open it in a browser and create a database.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2238" height="1538" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-finish-start_hu_8f425cfde898f3c5.webp 768w, https://percona.community/blog/2023/10/everest-finish-start_hu_822d435d26626b59.webp 480w, https://percona.community/blog/2023/10/everest-finish-start_hu_2a828f177c5b9f6a.webp 1400w" src="https://percona.community/blog/2023/10/everest-finish-start_hu_2a828f177c5b9f6a.webp" alt="Percona Everest Start" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2464" height="1360" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-create-db_hu_f77181e6c201d4d7.webp 768w, https://percona.community/blog/2023/10/everest-create-db_hu_9bba12acd776512b.webp 480w, https://percona.community/blog/2023/10/everest-create-db_hu_a8d3ae210945b857.webp 1400w" src="https://percona.community/blog/2023/10/everest-create-db_hu_a8d3ae210945b857.webp" alt="Percona Everest Create DB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="whats-next">What’s next?&lt;/h2>
&lt;p>Try creating databases with different configurations.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1976" height="1394" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/everest-dbs_hu_214c6ab6e5f940fe.webp 768w, https://percona.community/blog/2023/10/everest-dbs_hu_d41ecf3516b38d94.webp 480w, https://percona.community/blog/2023/10/everest-dbs_hu_f900a260c1ebac2e.webp 1400w" src="https://percona.community/blog/2023/10/everest-dbs_hu_f900a260c1ebac2e.webp" alt="Percona Everest Create DB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Repeat the installation with a different cluster or settings.&lt;/p>
&lt;p>If you face any problems or have ideas on how to improve components, create Issues on GitHub in the appropriate repositories.&lt;/p>
&lt;h3 id="stop-and-remove-percona-everest">Stop and remove Percona Everest&lt;/h3>
&lt;p>Once you finished your experiments, you can:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Stop Frontend - stop Bit running by pressing CTRL+C in the console.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Stop Backend API - stop Golang script by pressing CTRL+C in the console.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Stop and remove PostgreSQL in Docker - run &lt;code>make local-env-down&lt;/code> in the backend repository, or use Docker Desktop to stop.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Remove Kubernetes cluster.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="updates">Updates&lt;/h3>
&lt;p>Every day, developers make changes to the code and publish to repositories on GitHub.&lt;/p>
&lt;p>You can stop the component, pull the changes with &lt;code>git pull&lt;/code>, and start a new version. It’s just for experimentation and development. Some versions of components will not be compatible; uninstall all components and start over using the appropriate versions. Detailed instructions on how to upgrade will appear later.&lt;/p>
&lt;p>You can see changes to the build process or parameters in the repositories.&lt;/p>
&lt;h3 id="couple-of-useful-commands">Couple of useful commands&lt;/h3>
&lt;p>List of databases&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl -n percona-everest get db&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>List of pods&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kubectl -n percona-everest get pods&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>I hope you were able to make it this far, and it was interesting for you.&lt;/p>
&lt;p>I’d love if you leave your feedback in the comments.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>Percona Everest</category><category>Kubernetes</category><category>Opensource</category><category>DBaaS</category><media:thumbnail url="https://percona.community/blog/2023/10/everest-cover_hu_cf1bc6ca93ed5516.jpg"/><media:content url="https://percona.community/blog/2023/10/everest-cover_hu_e8628b81a62303b.jpg" medium="image"/></item><item><title>Kubernetes Community Days UK: Keynote Cilium and eBPF</title><link>https://percona.community/blog/2023/10/24/kcduk-cilium-ebpf/</link><guid>https://percona.community/blog/2023/10/24/kcduk-cilium-ebpf/</guid><pubDate>Tue, 24 Oct 2023 00:00:00 UTC</pubDate><description>This week, at Kubernetes Community Days UK in London. Liz Rice, Chief Open Source Officer at Isovalent, delivered a keynote on Cilium, eBPF, and the new feature of Cilium: Mutual Authentication.</description><content:encoded>&lt;p>This week, at &lt;a href="https://community.cncf.io/events/details/cncf-kcd-uk-presents-kubernetes-community-days-uk-2023/" target="_blank" rel="noopener noreferrer">Kubernetes Community Days UK&lt;/a> in London. &lt;strong>Liz Rice&lt;/strong>, Chief Open Source Officer at Isovalent, delivered a keynote on &lt;strong>Cilium, eBPF&lt;/strong>, and the new feature of &lt;strong>Cilium: Mutual Authentication&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="1200" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/10/kcduk-01_hu_102a38b3e43ed040.webp 768w, https://percona.community/blog/2023/10/kcduk-01_hu_e65a4af546f914fa.webp 480w, https://percona.community/blog/2023/10/kcduk-01_hu_ddbff7248aa96759.webp 1400w" src="https://percona.community/blog/2023/10/kcduk-01_hu_ddbff7248aa96759.webp" alt="lizrice-keynote-01" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>&lt;strong>Figure 1&lt;/strong>. Liz Rice Keynote KCD UK, London. Tuesday 17, 2023&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://cilium.io/" target="_blank" rel="noopener noreferrer">Cilium&lt;/a> is an &lt;strong>eBPF-powered open source&lt;/strong>, cloud native solution for delivering, securing, and observing network connectivity between workloads.&lt;/p>
&lt;p>eBPF is a technology that allows us to create modules to modify the behavior of the Linux kernel, but why would we want to change the Linux kernel?&lt;/p>
&lt;p>Some use cases for observability, security, and networking require tracking and monitoring our application, but we don’t want to constantly modify our application with these changes. It’s better to add a program that can observe the behavior of our application from the kernel.&lt;/p>
&lt;p>But changing the Linux kernel can be, well, hard.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="986" height="664" sizes="(max-width: 986px) 100vw, 986px" srcset="https://percona.community/blog/2023/10/kcduk-02_hu_ddd4d8c8bb240.webp 768w, https://percona.community/blog/2023/10/kcduk-02_hu_9e9fd1570e790090.webp 480w, https://percona.community/blog/2023/10/kcduk-02_hu_2e2a9abe3c8cf22.webp 986w" src="https://percona.community/blog/2023/10/kcduk-02_hu_2e2a9abe3c8cf22.webp" alt="addfea-to-the-kernel" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>&lt;strong>Figure 2&lt;/strong>. Adding features to the kernel (cartoon by Vadim Shchekoldin, Isovalent)&lt;/em>&lt;/p>
&lt;p>However, eBPF enables you to modify the kernel’s behavior without directly altering the kernel itself. It might sound unconventional, but eBPF makes this possible through the creation of programs for the Linux kernel. The Linux kernel accepts eBPF programs that can be loaded and unloaded as needed.
&lt;figure>&lt;img width="696" height="748" sizes="(max-width: 696px) 100vw, 696px" srcset="https://percona.community/blog/2023/10/kcduk-03_hu_40fe9b37e496ea75.webp 480w, https://percona.community/blog/2023/10/kcduk-03_hu_d3b2e3e194b200c8.webp 696w" src="https://percona.community/blog/2023/10/kcduk-03_hu_d3b2e3e194b200c8.webp" alt="addingfeatures-to-the-kernel-with-ebpf" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Figure 3. Adding kernel features with eBPF (cartoon by Vadim Shchekoldin, Isovalent)&lt;/em>&lt;/p>
&lt;p>To ensure that these eBPF programs written by us are secure, there is a mechanism in place that allows these programs to be verified safe for execution. This can be seen in &lt;a href="https://ebpf.io/what-is-ebpf/#ebpf-safety" target="_blank" rel="noopener noreferrer">eBPF verification and security&lt;/a>. You don’t have to restart the kernel to deploy or remove eBPF applications, which makes eBPF one of the technology tools of the moment.&lt;/p>
&lt;p>Liz also announced that Cilium recently graduated from CNCF. This means Cilium is considered stable and has been successfully used in production environments.
&lt;figure>&lt;img width="987" height="459" sizes="(max-width: 987px) 100vw, 987px" srcset="https://percona.community/blog/2023/10/kcduk-04_hu_d3531226435c595c.webp 768w, https://percona.community/blog/2023/10/kcduk-04_hu_94a80fafef4e7327.webp 480w, https://percona.community/blog/2023/10/kcduk-04_hu_7126670965499483.webp 987w" src="https://percona.community/blog/2023/10/kcduk-04_hu_7126670965499483.webp" alt="cncf-project-maturity-levels" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>&lt;strong>Figure 4&lt;/strong>. &lt;a href="https://www.cncf.io/project-metrics/" target="_blank" rel="noopener noreferrer">CNCF Project Maturity Levels&lt;/a>&lt;/em>&lt;/p>
&lt;p>After understanding what eBPF is, let’s move on to the actual topic of Liz’s keynote. She spoke about &lt;strong>Mutual Authentication with Cilium&lt;/strong>.&lt;/p>
&lt;p>Mutual Authentication with Cilium was the last significant feature missing from Cilium Service Mesh. It’s a somewhat more complex topic related to mTLS (Mutual transport layer security).&lt;/p>
&lt;p>mTLS is a mechanism that ensures the authenticity, integrity, and confidentiality of data exchanged between two entities in the network.&lt;/p>
&lt;p>In &lt;a href="https://isovalent.com/blog/post/cilium-release-114/" target="_blank" rel="noopener noreferrer">Cilium 1.14&lt;/a>, one of the most significant releases, Cilium introduces support for a feature that many developers have requested: mutual authentication. This feature simplifies the process of achieving mutual authentication between two workloads. It now only requires adding two lines of code to the YAML in the Cilium Network Policy to authenticate communication between two workloads.&lt;/p>
&lt;p>Slightly more complex, isn’t it? Let’s explore Mutual Authentication with Cilium in a second blog post very soon. We’ll also examine how this is related to Kubernetes and why it matters when running databases on Kubernetes.&lt;/p>
&lt;p>Check what the other &lt;a href="https://www.cncf.io/projects/" target="_blank" rel="noopener noreferrer">Graduated and Incubating Projects are at CNCF&lt;/a>, and don´t forget to subscribe to our &lt;a href="https://percona.community/blog/" target="_blank" rel="noopener noreferrer">Percona Community Blog&lt;/a> to read more about Open Source, CNCF Projects, and Database.&lt;/p></content:encoded><author>Edith Puclla</author><category>Events</category><category>Kubernetes</category><media:thumbnail url="https://percona.community/blog/2023/10/kcduk-01_hu_1fbc1ab2e4b72516.jpg"/><media:content url="https://percona.community/blog/2023/10/kcduk-01_hu_92c2c0c5c7a1073a.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.40 preview release</title><link>https://percona.community/blog/2023/10/03/preview-release/</link><guid>https://percona.community/blog/2023/10/03/preview-release/</guid><pubDate>Tue, 03 Oct 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.40 preview release Hello folks! Percona Monitoring and Management (PMM) 2.40 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-240-preview-release">Percona Monitoring and Management 2.40 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.40 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>To see the full list of changes, check out the &lt;a href="https://pmm-doc-pr-1139.onrender.com/release-notes/2.40.0.html" target="_blank" rel="noopener noreferrer">PMM 2.40 Release Notes&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker-installation">PMM server Docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Run PMM Server with Docker instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.40.0-rc&lt;/code>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-5830.tar.gz" target="_blank" rel="noopener noreferrer">Download&lt;/a> the latest pmm2-client release candidate tarball for 2.40.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To install pmm2-client package, enable testing repository via Percona-release:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;ol start="3">
&lt;li>Install pmm2-client package for your OS via Package Manager.&lt;/li>
&lt;/ol>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-moitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Run PMM Server as a VM instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.40.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.40.0.ova file&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Run PMM Server hosted at AWS Marketplace instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-09895e9b605f14cbc&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us on the [Percona Community Forums](&lt;a href="https://forums.percona.com/]" target="_blank" rel="noopener noreferrer">https://forums.percona.com/]&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Releases</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Open Source And Recession – An Economic Outlook</title><link>https://percona.community/blog/2023/09/14/open-source-and-recession-an-economic-outlook/</link><guid>https://percona.community/blog/2023/09/14/open-source-and-recession-an-economic-outlook/</guid><pubDate>Thu, 14 Sep 2023 00:00:00 UTC</pubDate><description>Initially, I dropped an idea of writing this blog by reckoning the amount of time and energy required to perform research. However, my mentor Ramona Gerum motivated me and provided inputs to write this blog. This would not have been possible without her help. Thank you very much Ramona.</description><content:encoded>&lt;p>Initially, I dropped an idea of writing this blog by reckoning the amount of time and energy required to perform research. However, my mentor Ramona Gerum motivated me and provided inputs to write this blog. This would not have been possible without her help. Thank you very much Ramona.&lt;/p>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>In October 2022, the term recession gained popularity on Google trends. This happened as many economists already predicted a slowdown, and it started flashing on news channels world-wide. In the following year, many giant corporations, such as Google, Microsoft, Amazon and others, announced massive layoffs; this was followed by other chain of events from stock market crashes to high inflation rates across the globe. This development reinforced people’s belief about recession.&lt;/p>
&lt;p>Majority of organisations who ran several rounds of layoffs claimed to have overstaffing issues as the hiring was done to anticipate the upcoming tide of work that never came, and they did not fire any technical/software experts. In such cases, open source products often rise as an alternative to proprietary ones. This blog will focus on the effects of a recession on open source products.&lt;/p>
&lt;h2 id="some-important-related-to-economics">Some important related to economics&lt;/h2>
&lt;p>As this blog focuses on recession, it is important to understand some economic terms to understand this blog properly.&lt;/p>
&lt;h3 id="gdpgross-domestic-product">GDP(Gross Domestic Product):&lt;/h3>
&lt;p>This is the gross monetary value of goods and services produced within a country. Every country produces various products, such as food, heavy machinery, oil and others. Those commodities would give some value upon selling them in the market. The gross total of the values of all such goods is GDP.&lt;/p>
&lt;p>Generally, it is calculated on an annual basis.&lt;/p>
&lt;h3 id="gdp-growth">GDP Growth:&lt;/h3>
&lt;p>The change in GDP compared to the previous GDP figure is GDP growth. In general, economists calculate them on a quarterly and yearly basis as they represent the economic position of a country and predict its growth in the near and distant future.&lt;/p>
&lt;h3 id="developed-countries">Developed countries:&lt;/h3>
&lt;p>This one is a little complex as there are more than one key factors that are decision-makers. For a country to become a developed one, all the factors from GDP to standard of living and HDI(Human Development Index) should be taken into consideration. In terms of economics, countries with a GDP of 12,000 USD(some experts put it at 24,000 USD) per capita(person) are considered a part of the emerged world.&lt;/p>
&lt;h3 id="boom">Boom:&lt;/h3>
&lt;p>A period of good economic growth: rising number jobs, increasing wages, increased profitability. This is characterized as a short stint of wealth creation and accumulation.&lt;/p>
&lt;h3 id="recession">Recession:&lt;/h3>
&lt;p>A period when economic growth is very low or nil. In this period, higher unemployment, lower demand in the job market, higher inflation and layoffs are commonly observed in such a period.&lt;/p>
&lt;p>In economics, negative GDP growth for 2 consecutive quarters is perceived as a recession, and if the condition of recession persists for 3 years, it is called an economic depression. However, economists analyze other indicators as well to decide if the recession prevails or not.&lt;/p>
&lt;h3 id="inflation-rate">Inflation rate:&lt;/h3>
&lt;p>An average increase in percentage in the gross price of various commodities from the previously obtained number. For example, if the milk was being sold at 1.5 USD in 2018, and in 2019, it is being sold at 1.55 USD, the price rise is nearly 3.33% that is the inflation rate of milk for a year. Usually, the gross percentage is considered to determine the condition of a nation. It is one of the important indicators.&lt;/p>
&lt;ul>
&lt;li>Ideally, 3-4% inflation rate is considered to be a healthy one.&lt;/li>
&lt;li>Higher than 5% affects the livelihood of people and many people would be pushed below the poverty line.&lt;/li>
&lt;li>While it is lower than 2%, it should be considered that the economy is in a reverse gear, which is not good for the growth of any country.&lt;/li>
&lt;/ul>
&lt;h3 id="third-world-countries">Third-world countries:&lt;/h3>
&lt;p>This term is often used in a pejorative manner; it was originally used for those countries who did not prefer to align themselves with either the USSR(socialist) or the USA(capitalist) in the era of the cold war that surfaced after the second world war. However, as they did not choose any side, they failed to grow like developed countries, hence the term “third-world countries” became synonymous with underdeveloped or developing countries.&lt;/p>
&lt;h2 id="causes-of-a-recession">Causes of a recession&lt;/h2>
&lt;p>A recession is caused by any disruption in an existing established economic flow. As a country grows, various channels are established through which money flows from sources to sources, and that is how money keeps flowing through the system. Whenever the rotation of money is affected, it gradually affects the economy of a country.&lt;/p>
&lt;p>For instance, consider the below existing channel.&lt;/p>
&lt;ol>
&lt;li>Farmers grow crop&lt;/li>
&lt;li>During crop harvesting seasons, farmers employ additional people&lt;/li>
&lt;li>Farmers sell goods to wholesalers&lt;/li>
&lt;li>Wholesalers sell it further to retail stores and business houses&lt;/li>
&lt;li>Retail stores sell it consumers, and business houses produce goods for consumer&lt;/li>
&lt;/ol>
&lt;p>Here, if a country is affected by a drought and there is no other method of farming available, people in the above hierarchy from farmers to employees of retail stores and business houses will become jobless for that year and the flow of money gets disrupted. This is to note that food is an essential commodity, and due to crop failure, the demand for food items exponentially rises and the prices of food items also skyrocket.&lt;/p>
&lt;p>Here, just to understand, effects of recession will not be visible immediately after the drought; it takes time to surface. Although the above one is just taken as an example, this is not a completely hypothetical scenario.&lt;/p>
&lt;p>As we saw a classical example of drought, there are various phenomena that can drive the economy to recession. It completely depends on the economical condition of a country.&lt;/p>
&lt;h3 id="in-the-third-world-countries">In the third-world countries:&lt;/h3>
&lt;p>This group is already facing a number of internal problems and depends on prosperous countries. They may or may not have adequate resources, however if they have, they do not know how to utilise them efficiently. Also, due to internal clashes, poverty and economic instability, they witness recessions very frequently. Some evident factors for recession in such countries are&lt;/p>
&lt;ul>
&lt;li>Drought&lt;/li>
&lt;li>Heavy rainfall&lt;/li>
&lt;li>Wars&lt;/li>
&lt;li>Epidemic&lt;/li>
&lt;li>International sanctions&lt;/li>
&lt;li>Political instability&lt;/li>
&lt;li>Uncontrollable inflation&lt;/li>
&lt;li>Corruption and scams&lt;/li>
&lt;li>Strikes&lt;/li>
&lt;/ul>
&lt;h3 id="in-the-developed-countries">In the developed countries:&lt;/h3>
&lt;p>The developed world is often perceived as the utopia as processes are well-established there. The scope of recession appears limited, however this is just one side of the coin. There is a dialogue in the movie Wall Street: “Greed is good! Greed is progress!….”. Why I quoted this here is because greed is hardwired in the human gene, which propels humans to earn more money and exploit the system’s loopholes. Over the period, it causes an economic slowdown. Some of the driving factors for recession in such countries are&lt;/p>
&lt;ul>
&lt;li>Policy loopholes&lt;/li>
&lt;li>Stock market crash&lt;/li>
&lt;li>Bad loans&lt;/li>
&lt;li>Wars&lt;/li>
&lt;li>Epidemic&lt;/li>
&lt;/ul>
&lt;p>Also, any economic slowdowns in influential countries resonate world-wide. Considering the classical case of the great recession of 2008, it started due to the housing bubble that resulted in the subprime mortgage crisis. In other words, due to sudden hikes in the prices of houses, banks started approving loan amounts with cheaper mortgage assets/commodities. For example, the loan amount is 100K USD, while the mortgaged item is worth 10K USD only. Also, people with poor credit rating got loans. This resulted into a burst of a bubble, which was an impetus to economic turmoil, millions of job losses and closure of businesses.&lt;/p>
&lt;h2 id="repetitions-and-durations-of-recessions">Repetitions And Durations Of Recessions&lt;/h2>
&lt;p>As described in the last paragraph, reasons for recessions may vary from country-to-country, also recessions in various countries may not necessarily coincide. Hence, the stint and repetition pattern for every recession are different.&lt;/p>
&lt;p>Earlier, it was believed that recessions and booms are followed by each other. However, over the period, the political structure and law system in developed countries have evolved in such a way that their economies may stay resilient against recession.&lt;/p>
&lt;p>Taking the case of the US, before the 1980s, the period of economic slowdowns were too frequent, nearly 3-5 years. However, since the 80s, the gap between the 2 recessions has significantly widened.&lt;/p>
&lt;p>The snippet below taken from &lt;a href="https://www.investopedia.com/articles/economics/08/past-recessions.asp" target="_blank" rel="noopener noreferrer">Investopedia&lt;/a> shows the period of recession(“NBER Recessions” and “Length of Recession(Months)”) in the USA.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="607" height="667" sizes="(max-width: 607px) 100vw, 607px" srcset="https://percona.community/blog/2023/09/opensource-01_hu_377a8f06cd9e3a0c.webp 480w, https://percona.community/blog/2023/09/opensource-01_hu_92a90563cc318009.webp 607w" src="https://percona.community/blog/2023/09/opensource-01_hu_92a90563cc318009.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The condition of recession may not prevail across multiple countries at the same time, having said that, the great recession of 2008 was the first case of global recession, which cannot be considered as an exceptional one as it was the beginning of the era of globalisation. New waves of recessions will have global impact.&lt;/p>
&lt;h2 id="performance-of-open-source-during-and-after-recessions">Performance of open source during and after recessions&lt;/h2>
&lt;p>During every recession, it has been observed that organisations lean towards less costly products, so that they can cut down the cost and maintain their profitability. Open source software emerges as a boon for them because they can save licensing cost for the product cost.&lt;/p>
&lt;p>The amount required to acquire the license is very high. During a recession period, it is not at all an affordable option for companies, having said that, companies may run without new development work for a certain period and may eliminate some staff members, however platforms and databases are very crucial; hence, they cannot be eliminated as it is tantamount to shutting down a company.&lt;/p>
&lt;p>Deploying open source products in an environment is the most viable option in such cases as they may continue running their businesses.&lt;/p>
&lt;p>A survey was conducted by Linux foundations on OSS(Open Source Software) in which nearly 431 people, who were from “Fortune 500 group of companies”, working from middle-level to top-level management participated. They were asked various questions about open source and its benefits. When they were asked about adopting open source products, their answers are as mentioned in the chart below.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1248" height="736" sizes="(max-width: 1248px) 100vw, 1248px" srcset="https://percona.community/blog/2023/09/opensource-02_hu_6ed4a3a252c11c08.webp 768w, https://percona.community/blog/2023/09/opensource-02_hu_21d521be84526b6d.webp 480w, https://percona.community/blog/2023/09/opensource-02_hu_8715a9b231c7ab03.webp 1248w" src="https://percona.community/blog/2023/09/opensource-02_hu_8715a9b231c7ab03.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://project.linuxfoundation.org/hubfs/LF%20Research/Measuring%20the%20Economic%20Value%20of%20Open%20Source%20-%20Report.pdf?hsLang=en" target="_blank" rel="noopener noreferrer">Source – Page 10&lt;/a>&lt;/p>
&lt;p>By analysing the chart, we can see surges in demand for open source in both the dot-com bubble burst(2001) and the great recession (2008). Besides, a sharp rise from 2016 can be noticed as well. However, it can be seen that the demand for open source has always been moderate.&lt;/p>
&lt;p>In this section, it can be seen that open source software is resilient against recessions and keeps on flourishing during and after odd times as well.&lt;/p>
&lt;h2 id="can-open-source-keeps-recessions-at-bay">Can Open Source Keeps Recessions At Bay?&lt;/h2>
&lt;p>This is a really interesting question that requires further research. Since the last few years, companies have become more optimistic about open source software products, such as MySQL, PostgreSQL, Kubernetes and so on. This is because these products provide all those features that meet industry standards and requirements. Over the period, open source databases, OS and web/app servers have evolved by working with various industries, and now, they meet all the requirements that companies give. Due to which, open source successfully carved a niche in the market.&lt;/p>
&lt;p>The noticeable thing is that organisations do not need to pay a penny to use these products. Also, if one can afford to hire a small team of developers, the company comes up with its own version of the product. In case this is not the viable option, there are communities and a number of small-scale to medium-scale companies that provide support of open source.&lt;/p>
&lt;p>If we can focus on the cost of products, it would give a more clear view on open source’s ability to stem recession.&lt;/p>
&lt;h3 id="the-case-of-2008s-great-recession">The case of 2008’s great recession:&lt;/h3>
&lt;p>We all have heard tales of 2008 recessions: heavy pay cuts, job losses, suicides, loss of property and so on and on and on. As I mentioned in this blog earlier with an example, a disruption in the flow of money causes recession, and in the case of 2008, banks sanctioned loans that cannot be repaid. Due to which, banks were defaulted and it had badly hit the economy of the USA, which affected the whole world.&lt;/p>
&lt;p>The most peculiar thing the &lt;a href="https://www.washingtonpost.com/business/economy/a-guide-to-the-financial-crisis--10-years-later/2018/09/10/114b76ba-af10-11e8-a20b-5f4f84429666_story.html" target="_blank" rel="noopener noreferrer">Washington Post&lt;/a> highlighted about its impact and losses.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Loss in the global economy – nearly 2 trillion USD&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The US stock market loss – around 8 trillion USD&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Gross individual losses – around 9.8 trillion USD&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="a-hypothetical-scenario-with-an-open-source-in-2008">A hypothetical scenario with an open source in 2008:&lt;/h3>
&lt;p>Had it changed anything if organisations had placed open source products instead of proprietary software products back in 2008? As we can understand, the stock market loss and individual losses cannot be recovered due to this, because it has no relation with the product in the backend or frontend.&lt;/p>
&lt;p>To understand this part, we have to check out some revenues. The revenue of Microsoft was nearly 52 billion USD in 2008, and if we start looking into revenues of other proprietary software producing organisations, their collective revenue may hardly reach half of the global economy loss(2 trillion USD). If we consider all the losses incurred due to proprietary products only, even in that case, open source could not have staved off the recession as the number itself is very huge.&lt;/p>
&lt;p>As per Statitsa, the gross revenue of open source services was 32 billion USD in 2022. Considering this figure, it could have definitely helped mitigate the situation up to certain extent in 2008, and many of jobs could have been saved and many organisations might have avoided closures.&lt;/p>
&lt;h3 id="open-sources-role-after-2008">Open source’s role after 2008:&lt;/h3>
&lt;p>Due to loopholes in policy, the situation of economic turbulence arose, hence the US government took corrective actions and introduced a number of overhauls in economic policies, which made the economy more stable and immune to unwanted slowdowns.&lt;/p>
&lt;p>Along with these things, open source gained popularity and percolated into different industries. Many companies have moved away from proprietary products and adopted open source products as they found them cost-effective with all the required features available. Of course, a penny saved is a penny earned. However, it seems no proper analysis was made to notice the economical impact of open source’s entry in the market. Also upon looking at the potential of open source a number of giant corporations, such as Google, Microsoft and others, have started contributing to the open source community.&lt;/p>
&lt;p>But, if those people who contribute to open source are actually getting benefited? The answer lies in the below survey result.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1244" height="830" sizes="(max-width: 1244px) 100vw, 1244px" srcset="https://percona.community/blog/2023/09/opensource-03_hu_66c25df8eb88db89.webp 768w, https://percona.community/blog/2023/09/opensource-03_hu_c44cf8cc522ed54d.webp 480w, https://percona.community/blog/2023/09/opensource-03_hu_4503abc536b65062.webp 1244w" src="https://percona.community/blog/2023/09/opensource-03_hu_4503abc536b65062.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://project.linuxfoundation.org/hubfs/LF%20Research/Measuring%20the%20Economic%20Value%20of%20Open%20Source%20-%20Report.pdf?hsLang=en" target="_blank" rel="noopener noreferrer">Source – Page 13&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.cnbc.com/2013/06/11/pimco-sees-60-chance-of-global-recession-in-35-years.html" target="_blank" rel="noopener noreferrer">Many economists predicted recession in the US between 2015-2018&lt;/a>, which did not actually happen. Also, while I am writing this blog, experts have already announced a recession in 2023-24, which is yet to come true. Though I firmly believe that open source is pushing the recession back, it is difficult to prove it in absence of surveys and data. I hope some experts may pick this topic and make a detailed analysis on this part; I am sure it will give a great push to the open source community.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Booms and recessions were repeating on regular intervals earlier, however over the period, due to economic advancements, recessions became infrequent and started repeating after several years. It is not necessary that all countries may face economic slowdown at the same time, also reasons for slowdowns may differ as well. During such a period, organizations have to bear loss and fall in revenues, however open source becomes more adoptive during recessions. Also, it is possible that open source is keeping recessions away, but there is no data to back this claim.&lt;/p></content:encoded><author>Ninad Shah</author><category>Opensource</category><category>Community</category><media:thumbnail url="https://percona.community/blog/2023/09/opensource-cover_hu_6cb7591bae270152.jpeg"/><media:content url="https://percona.community/blog/2023/09/opensource-cover_hu_e5306d1727cda1b5.jpeg" medium="image"/></item><item><title>Dolphie, your real-time MySQL monitoring assistant</title><link>https://percona.community/blog/2023/08/22/dolphie-your-real-time-mysql-monitoring-assistant/</link><guid>https://percona.community/blog/2023/08/22/dolphie-your-real-time-mysql-monitoring-assistant/</guid><pubDate>Tue, 22 Aug 2023 00:00:00 UTC</pubDate><description>For as long as I can remember, Innotop has been the go-to terminal tool for real-time MySQL monitoring. It is an invaluable addition to any DBA’s toolkit, but unfortunately, it’s not really actively maintained these days, except for addressing critical issues, and it hasn’t kept pace with the evolving capabilities of modern terminals. With no viable alternatives except for InnotopGo, which is also no longer actively maintained and limited to MySQL 8 (while many still use 5.7), I decided to build my own in Python.</description><content:encoded>&lt;p>For as long as I can remember, &lt;a href="https://github.com/innotop/innotop" target="_blank" rel="noopener noreferrer">Innotop&lt;/a> has been the go-to terminal tool for real-time MySQL monitoring. It is an invaluable addition to any DBA’s toolkit, but unfortunately, it’s not really actively maintained these days, except for addressing critical issues, and it hasn’t kept pace with the evolving capabilities of modern terminals. With no viable alternatives except for &lt;a href="https://github.com/lefred/innotopgo" target="_blank" rel="noopener noreferrer">InnotopGo&lt;/a>, which is also no longer actively maintained and limited to MySQL 8 (while many still use 5.7), I decided to build my own in Python.&lt;/p>
&lt;center>I call it, Dolphie&lt;/center>
&lt;p>
&lt;figure>&lt;img width="150" height="206" sizes="(max-width: 150px) 100vw, 150px" srcset="https://percona.community/blog/2023/08/dolphie-150_hu_b4410d0b93ab2c01.webp 150w" src="https://percona.community/blog/2023/08/dolphie-150_hu_b4410d0b93ab2c01.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Initially, I relied on Python’s Rich package for the user interface. However, I recently stumbled upon &lt;a href="https://textual.textualize.io" target="_blank" rel="noopener noreferrer">Textual&lt;/a> a few months ago, and it piqued my interest. It’s a framework that extends the capabilities of Rich, opening up a world of possibilities in the terminal. After experimenting with it for a few days, it inspired me to redevelop Dolphie with it, and I’ve been thoroughly pleased with the results. It has allowed me to showcase many of the features that will be displayed in this blog post!&lt;/p>
&lt;h3 id="getting-started">Getting started&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="2854" height="966" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_d57a8b39b3f9f264.webp 768w, https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_204b01e8098ac47.webp 480w, https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_13c848fa31b62bc1.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_13c848fa31b62bc1.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>When you first start Dolphie, you’ll be greeted with a dashboard displaying various important MySQL metrics, along with a sparkline below it to measure the QPS (Queries per second) + process list. There are multiple ways to manipulate the process list, such as changing how it sorts, filtering by user/host/query text/database/time, killing threads, and much more.&lt;/p>
&lt;p>There are currently four panels that can be toggled interchangeably for display:&lt;/p>
&lt;ul>
&lt;li>Dashboard&lt;/li>
&lt;li>Process list&lt;/li>
&lt;li>Replication/Replicas&lt;/li>
&lt;li>Graph Metrics&lt;/li>
&lt;/ul>
&lt;p>A big perk of transitioning to Textual is the integration of graphs. It’s as if I’ve incorporated a mini-PMM (Percona Monitoring and Management) right into Dolphie! The switches you see can be toggled on and off to display or hide their corresponding metrics on the graph.&lt;/p>
&lt;h4 id="buffer-pool-requests-graph--replication-panel">Buffer Pool Requests Graph + Replication Panel&lt;/h4>
&lt;p>
&lt;figure>&lt;img width="2854" height="2326" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_34868ffa27e626f0.webp 768w, https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_e814255e60a58e02.webp 480w, https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_a4f630c70c1f9433.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_a4f630c70c1f9433.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="checkpoint-graph">Checkpoint Graph&lt;/h4>
&lt;p>
&lt;figure>&lt;img width="2866" height="1608" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_checkpoint_hu_d262785e91a97dbb.webp 768w, https://percona.community/blog/2023/08/dolphie_checkpoint_hu_8f5a055d3bde1fcf.webp 480w, https://percona.community/blog/2023/08/dolphie_checkpoint_hu_5ad4981684106aba.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_checkpoint_hu_5ad4981684106aba.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="redo-logs-graph">Redo Logs Graph&lt;/h4>
&lt;p>How are your redo logs performing? Dolphie shows you how much data is being written per second, the active count of redo logs (MySQL 8 only), and how much data is being written to it per hour (inspired by &lt;a href="https://www.percona.com/blog/how-to-calculate-a-good-innodb-log-file-size" target="_blank" rel="noopener noreferrer">this&lt;/a> blog post)
&lt;figure>&lt;img width="2856" height="1270" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_redo_log_hu_5c233afb07f64aa4.webp 768w, https://percona.community/blog/2023/08/dolphie_redo_log_hu_1e7b6c7678ba6a83.webp 480w, https://percona.community/blog/2023/08/dolphie_redo_log_hu_fe195244b3f893bd.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_redo_log_hu_fe195244b3f893bd.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="dml-graph">DML Graph&lt;/h4>
&lt;p>
&lt;figure>&lt;img width="2858" height="1754" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_dml_hu_b33874251241fe96.webp 768w, https://percona.community/blog/2023/08/dolphie_dml_hu_aa61c7415aad886e.webp 480w, https://percona.community/blog/2023/08/dolphie_dml_hu_6d9299e0a870ce40.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_dml_hu_6d9299e0a870ce40.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="thread-data">Thread data&lt;/h4>
&lt;p>Dolphie lets you display a thread’s information with an explanation of its query along + transaction history
&lt;figure>&lt;img width="2856" height="1808" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_thread_details_hu_a439a35db79bf360.webp 768w, https://percona.community/blog/2023/08/dolphie_thread_details_hu_dc4294290ebe9185.webp 480w, https://percona.community/blog/2023/08/dolphie_thread_details_hu_1e175d154f9382de.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_thread_details_hu_1e175d154f9382de.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="kill-threads">Kill threads&lt;/h4>
&lt;p>Dolphie lets you terminate threads using a selected option. Notice how it autocompletes the input for you. This is a feature across the board. It will autocomplete any input that it can
&lt;figure>&lt;img width="2860" height="1712" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_5666aaad9856f344.webp 768w, https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_76e6f8c40f068a99.webp 480w, https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_3f73ba3165bca0e4.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_3f73ba3165bca0e4.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="quick-switch-host">Quick switch host&lt;/h4>
&lt;p>After using Dolphie extensively myself, I realized the need to simplify host switching. I found myself restarting it frequently just to change the host. This feature saves all the hosts you’ve connected to, allowing for autocomplete when you want to switch
&lt;figure>&lt;img width="2856" height="1626" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_fd7492f8398ddf65.webp 768w, https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_4cce7df905bc1030.webp 480w, https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_206ba9adc16e6f17.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_206ba9adc16e6f17.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="error-log">Error log&lt;/h4>
&lt;p>In MySQL 8, I was delighted to see that the error log was in performance_schema. Of course, I had to support it! It has switches to toggle on/off event types and search functionality
&lt;figure>&lt;img width="2854" height="612" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dolphie_error_log_hu_b6264588d4f58dcb.webp 768w, https://percona.community/blog/2023/08/dolphie_error_log_hu_75c19845b067af18.webp 480w, https://percona.community/blog/2023/08/dolphie_error_log_hu_46683a5909b81b08.webp 1400w" src="https://percona.community/blog/2023/08/dolphie_error_log_hu_46683a5909b81b08.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="errant-transactions">Errant transactions&lt;/h4>
&lt;p>The Replicas panel will let you know if your replicas have any errant transactions and what they are
&lt;figure>&lt;img width="1298" height="780" sizes="(max-width: 1298px) 100vw, 1298px" srcset="https://percona.community/blog/2023/08/dolphie_errant_transaction_hu_796721262c17502c.webp 768w, https://percona.community/blog/2023/08/dolphie_errant_transaction_hu_6fdf999824be3af7.webp 480w, https://percona.community/blog/2023/08/dolphie_errant_transaction_hu_3a124749b88923e1.webp 1298w" src="https://percona.community/blog/2023/08/dolphie_errant_transaction_hu_3a124749b88923e1.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>These are just some of the features that Dolphie has. There are many more that I haven’t covered, which you can discover for yourself and try out!&lt;/p>
&lt;p>If you’d like to try Dolphie, it’s just a pip away:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pip install dolphie&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I’m open to feedback and suggestions so don’t be a stranger :) If you’d like to contribute to the project, I’d be delighted to have you!&lt;/p>
&lt;p>You can find Dolphie on its &lt;a href="https://github.com/charles-001/dolphie" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>&lt;/p></content:encoded><author>Charles Thompson</author><category>Dev</category><category>MySQL</category><category>Monitoring</category><category>Python</category><media:thumbnail url="https://percona.community/blog/2023/08/dolphie_header_hu_c50d81109e2ceaa2.jpeg"/><media:content url="https://percona.community/blog/2023/08/dolphie_header_hu_1bafd2a4bddf608e.jpeg" medium="image"/></item><item><title>PMM Client on Raspberry Pi 4</title><link>https://percona.community/blog/2023/08/15/pmm-client-on-raspberry-pi-4/</link><guid>https://percona.community/blog/2023/08/15/pmm-client-on-raspberry-pi-4/</guid><pubDate>Tue, 15 Aug 2023 00:00:00 UTC</pubDate><description>This will be the third in my series of Percona Products on a Raspberry Pi. My previous posts:</description><content:encoded>&lt;p>This will be the third in my series of Percona Products on a Raspberry Pi. My previous posts:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://percona.community/blog/2019/08/01/how-to-build-a-percona-server-stack-on-a-raspberry-pi-3/" target="_blank" rel="noopener noreferrer">How to Build a Percona Server “Stack” on a Raspberry Pi 3+
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2022/04/05/percona-server-raspberry-pi/" target="_blank" rel="noopener noreferrer">Raspberry Pi Bullseye Percona Server 64bit&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Before I get started I would like to thank &lt;a href="https://www.percona.com/blog/compiling-a-percona-monitoring-and-management-v2-client-in-arm-raspberry-pi-3/" target="_blank" rel="noopener noreferrer">guriandoro&lt;/a>, for the work he did in compiling the PMM Client tools in his 2021 blog.&lt;/p>
&lt;p>My regular readers know how much I love the Raspberry Pi and MySQL. I have several hobby projects that collect data, and MySQL on Pi is great&lt;br>
solution!&lt;/p>
&lt;p>I recently decided that I needed to be able to monitor my two MySQL Server.&lt;/p>
&lt;p>The steps below should work on the Raspberry Pi 3+ and 4.&lt;/p>
&lt;h3 id="what-better-tool-to-use-than-pmm">What better tool to use than PMM&lt;/h3>
&lt;p>&lt;strong>In this blog we will be using PMM Client 2.27 and PMM Server version 2.39.&lt;/strong>&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;p>&lt;strong>Important Note:&lt;/strong> Your Raspberry Pi must be on Raspbian Bullseye.&lt;/p>
&lt;p>I will assume if you have made it this far into the BLOG post, then you already have a running PMM Server. If not, you can read about installing a PMM Server &lt;a href="https://www.percona.com/software/pmm/quickstart" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h3 id="add-the-pmm-user-to-your-mysql-server">Add the pmm user to your MySQL Server&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CREATE USER 'pmm'@'127.0.0.1' IDENTIFIED BY 'pass' WITH MAX_USER_CONNECTIONS 10;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GRANT SELECT, PROCESS, REPLICATION CLIENT, RELOAD, BACKUP_ADMIN ON *.* TO 'pmm'@'localhost';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="the-following-steps-should-be-run-on-your-raspberry-pi">The following steps should be run on your Raspberry Pi&lt;/h2>
&lt;hr>
&lt;h3 id="download-the-pre-complied-pmm-client-tools">Download the pre-complied pmm-client tools&lt;/h3>
&lt;p>These were complied based on 2.27 source code. You can see the steps I followed to compile the tools &lt;a href="https://github.com/cetanhota/pi4-arm-pmm-client" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget https://github.com/cetanhota/pi4-arm-pmm-client/archive/refs/heads/main.zip
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unzip main.zip&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once we have the client unzipped its a good idea to verify that the tools will work on your Raspberry Pi.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cd ~/pi4-arm-pmm-client-main/raspbian-bullseye
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./pmm-agent --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You should get output like below.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ProjectName: pmm-agent
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ProjectName: pmm-agent
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Version: 2.27.0-84-g78b8519-dirty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PMMVersion: 2.27.0-84-g78b8519-dirty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timestamp: 2023-08-16 00:57:34 (UTC)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FullCommit: 78b85198ad1e2c319d4012ef5ae338f389e2000a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Branch: main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If the pmm-agent displayed info like above, then you good to keep moving down the document. If you ran into issues please post in the comments and we can see what can be done to get you working. The only problem I ran in to was that the root user could not execute the client tools.&lt;/p>
&lt;h2 id="setup-install-directories">Setup install directories&lt;/h2>
&lt;p>We need to create the install directory and move files to the correct locations.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/pmm2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/pmm2/tools
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/pmm2/config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/pmm2/exporters
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/exporters
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/exporters/collectors/textfile-collector/high-resolution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/exporters/collectors/textfile-collector/medium-resolution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir -p /usr/local/percona/exporters/collectors/textfile-collector/low-resolution&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="copy-files-into-directories">Copy files into directories&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cd ~/pi4-arm-pmm-client-main/raspbian-bullseye
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo cp pmm-admin pmm-agent /usr/local/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo cp node_exporter vmagent /usr/local/percona/pmm2/exporters&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="configure-the-pmm-client">Configure the PMM Client&lt;/h2>
&lt;p>Its time to configure and test the PMM Client for the MySQL server where we are install PMM Client.&lt;/p>
&lt;p>Please make sure you have the following three items:&lt;/p>
&lt;ol>
&lt;li>PMM Server Hostname or IP Address&lt;/li>
&lt;li>PMM Server admin ID&lt;/li>
&lt;li>PMM Server admin Password&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo pmm-agent setup --config-file=/usr/local/percona/pmm2/config/pmm-agent.yaml\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --server-address=&lt;YOUR-PMM-Server> --server-insecure-tls\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --server-username=&lt;YOURADMIN> --server-password=&lt;YOURPASSWORD>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now that the PMM Client has been configured, lets move on to starting the PMM Client.&lt;/p>
&lt;h2 id="start-the-pmm-client">Start the PMM Client&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> sudo pmm-agent --config-file=/usr/local/percona/pmm2/config/pmm-agent.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Verify by reviewing the client is running correctly. If all is well you should see output like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.294-04:00] Loading configuration file /usr/local/percona/pmm2/config/pmm-agent.yaml. component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/node_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/mysqld_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/mongodb_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/postgres_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/proxysql_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/rds_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.295-04:00] Using /usr/local/percona/pmm2/exporters/azure_exporter component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.296-04:00] Using /usr/local/percona/pmm2/exporters/vmagent component=main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.296-04:00] Starting... component=client
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.296-04:00] Starting local API server on http://127.0.0.1:7777/ ... component=local-server/JSON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.296-04:00] Connecting to https://admin:***@192.168.1.127:443/ ... component=client
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.298-04:00] Started. component=local-server/JSON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.320-04:00] Connected to 192.168.1.127:443. component=client
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.320-04:00] Establishing two-way communication channel ... component=client
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO[2023-08-15T17:00:34.341-04:00] Two-way communication channel established in 21.349589ms. Estimated clock drift: -84.399159ms. component=client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At this point press ctl+c to stop the pmm-agent.&lt;/p>
&lt;p>If you want to start the pmm-agent again and running the background use this command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo nohup pmm-agent --config-file=/usr/local/percona/pmm2/config/pmm-agent.yaml &amp;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You may choose a different startup process for the PMM Client based on personal preference.&lt;/p>
&lt;h2 id="add-pt-summary-to-the-pmm-client">Add pt-summary to the PMM Client&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cd /usr/local/percona/pmm2/tools
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo wget percona.com/get/pt-summary
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod ugo+x pt-summary&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s see summary in action:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1938" height="1006" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/node-summary_hu_972ff7493abda7be.webp 768w, https://percona.community/blog/2023/08/node-summary_hu_7856cec4ff2b4d5c.webp 480w, https://percona.community/blog/2023/08/node-summary_hu_acc7da042d7777bd.webp 1400w" src="https://percona.community/blog/2023/08/node-summary_hu_acc7da042d7777bd.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="add-your-raspberry-pi-mysql-server-to-pmm-server">Add your Raspberry Pi MySQL Server to PMM Server&lt;/h2>
&lt;p>I am sure most of you know how to add a server to your existing PMM Server but if you have not done this before here is what you need to do.&lt;/p>
&lt;ol>
&lt;li>Connect to your PMM Server.&lt;/li>
&lt;li>Use pmm-admin command to add server for monitoring.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/usr/local/percona/pmm2/bin/pmm-admin add mysql --service-name=&lt;MYSQL SERVER NAME> --server-insecure-tls --server-url=https://&lt;YOUR ADMIN>:&lt;YOUR PASSWORD>@localhost --username=&lt;DB USER> --password=&lt;DB PASSWORD> --host=&lt;MYSQL SERVER NAME>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="pmm-server-screen-shoot">PMM Server Screen Shoot&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="1854" height="1022" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/pmm-view2_hu_ef33308ebf974273.webp 768w, https://percona.community/blog/2023/08/pmm-view2_hu_f644cfb3059a28f.webp 480w, https://percona.community/blog/2023/08/pmm-view2_hu_462858b0c84bc4ab.webp 1400w" src="https://percona.community/blog/2023/08/pmm-view2_hu_462858b0c84bc4ab.webp" alt="image" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="in-closing">In Closing&lt;/h2>
&lt;p>I really loved working on this little project. I hope you find some use for the information.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>MySQL</category><category>PMM</category><category>DIY</category><media:thumbnail url="https://percona.community/blog/2023/08/fresh-raspi_hu_10d8e4f704b9d08a.jpg"/><media:content url="https://percona.community/blog/2023/08/fresh-raspi_hu_6724276b654ff0db.jpg" medium="image"/></item><item><title>DoKC Operator SIG Update</title><link>https://percona.community/blog/2023/08/14/dokc-operator-sig-update/</link><guid>https://percona.community/blog/2023/08/14/dokc-operator-sig-update/</guid><pubDate>Mon, 14 Aug 2023 00:00:00 UTC</pubDate><description>Before our meeting, we started with a question to begin the morning: What board game or tabletop game have you played that you would recommend to others?</description><content:encoded>&lt;p>Before our meeting, we started with a question to begin the morning: &lt;strong>What board game or tabletop game have you played that you would recommend to others?&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://www.linkedin.com/in/itamar-marom/" target="_blank" rel="noopener noreferrer">Itamar Marom&lt;/a> suggests that Catan as a good board game, which takes a lot of time, super annoying when you lose, but generally a lot of fun. So highly suggested!&lt;/p>
&lt;p>Other members like &lt;a href="https://www.linkedin.com/in/hugh-lashbrooke/" target="_blank" rel="noopener noreferrer">Hugh Lashbrooke&lt;/a> and &lt;a href="https://www.linkedin.com/in/berkeleybob2105/" target="_blank" rel="noopener noreferrer">Robert Hodges&lt;/a> prefer Monopoly, where you must reach a higher level of monopoly awareness to enjoy it fully. The idea is that you can make pretty much any deal with anyone else within the rules of monopoly. That’s when it gets fun.&lt;/p>
&lt;p>Terraforming Mars and Meadow are other favorite games for members of DoK.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2000" height="1600" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/dok-game_hu_e981eb42961dc0f2.webp 768w, https://percona.community/blog/2023/08/dok-game_hu_68ce6d07faaa160a.webp 480w, https://percona.community/blog/2023/08/dok-game_hu_a856edd3727d408.webp 1400w" src="https://percona.community/blog/2023/08/dok-game_hu_a856edd3727d408.webp" alt="dokc-game" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Let’s get into our agenda for this meeting.&lt;/p>
&lt;p>For &lt;strong>our first agenda&lt;/strong>, &lt;a href="https://www.linkedin.com/in/itamar-marom/" target="_blank" rel="noopener noreferrer">Itamar Marom&lt;/a> (From AppsFlyer and DoKC Ambassador) proposes joining more data operators’ maintainers and developers in the community.&lt;/p>
&lt;p>Itamar was analyzing the data technology map and saw that some workloads are very common and act differently from what is found in SIG Operator (Special Interest Group) in the case of Spark and Kafka. It would be nice to see the maintainers and hear their opinions, especially in meetings like our one with Google, where they have very different and exciting use cases.&lt;/p>
&lt;p>We’ve had folks from these communities present at our virtual meetups. Bringing them to the SIG to learn, share, and find ways to collaborate would benefit the broader DoK ecosystem.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="6912" height="3456" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/kafka-spark_hu_e4efd244d366d8a7.webp 768w, https://percona.community/blog/2023/08/kafka-spark_hu_7b0f54c82c300e93.webp 480w, https://percona.community/blog/2023/08/kafka-spark_hu_7401f6ac11f43d0e.webp 1400w" src="https://percona.community/blog/2023/08/kafka-spark_hu_7401f6ac11f43d0e.webp" alt="dokc-game" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;a href="https://www.linkedin.com/in/jimhalfpenny/" target="_blank" rel="noopener noreferrer">Jim Halfpenny&lt;/a> from &lt;strong>Stackable&lt;/strong> mentions that they have several operators for open source projects, including Apache Kafka, Apache NiFi, Apache Superset, Trino, and more. And they will love input from the community on the direction the operators should take. His aim is that our operators should play nicely with others!&lt;/p>
&lt;p>&lt;a href="https://www.linkedin.com/in/mklogan/" target="_blank" rel="noopener noreferrer">Melissa Logan&lt;/a> was part of several discussions with the Argo community, and maybe there’s a chance they could join us as well.&lt;/p>
&lt;p>Itamar will prepare a proposal for this project in which the group can collaborate and will share it soon.&lt;/p>
&lt;p>The &lt;strong>second item on our agenda&lt;/strong> was &lt;strong>Carrier Hardening and Security Project&lt;/strong> Update by &lt;a href="https://www.linkedin.com/in/berkeleybob2105/" target="_blank" rel="noopener noreferrer">Robert Hodges&lt;/a> (DoKC Ambassador).&lt;/p>
&lt;p>This project is a guide to establishing a baseline for secure data management on Kubernetes by fortifying the database operators. The guide aims to identify the typical attack surfaces that exist for databases running on Kubernetes. It will establish a collection of best practices for enhancing their security using operators.&lt;/p>
&lt;p>Robert is working on an August 12 talk at &lt;a href="https://www.dataconla.com/" target="_blank" rel="noopener noreferrer">DataConLA&lt;/a> on the topic: Tips for Sleeping Well with State-of-the-Art Data Management. It will contain the framing of the operator hardening guide.&lt;/p>
&lt;p>Robert is playing around with a couple of approaches to divide up the problem space. One of them is to deal with security concerns as follows:&lt;/p>
&lt;ol>
&lt;li>The database itself - E.g., setting passwords safely.&lt;/li>
&lt;li>Kubernetes - Security it from outside attackers - E.g., encrypted client connections.&lt;/li>
&lt;li>Data outside Kubernetes - Object storage used for backups, logs forwarded to log management systems.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="934" height="621" sizes="(max-width: 934px) 100vw, 934px" srcset="https://percona.community/blog/2023/08/dataconla_hu_5334419b836da311.webp 768w, https://percona.community/blog/2023/08/dataconla_hu_61b5eb5c37cf8f66.webp 480w, https://percona.community/blog/2023/08/dataconla_hu_a715e2306fd07df2.webp 934w" src="https://percona.community/blog/2023/08/dataconla_hu_a715e2306fd07df2.webp" alt="dokc-game" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Robert Will share a draft of the talk in the &lt;strong>#sig-operator&lt;/strong> channel for discussion.&lt;/p>
&lt;p>The last topic on our agenda is ArgoCD and general CI/CD compatibility for operators by Robert Hodges.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1500" height="1500" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/08/argo_hu_1ce69fec34daa30.webp 768w, https://percona.community/blog/2023/08/argo_hu_4bf9bd22e549d761.webp 480w, https://percona.community/blog/2023/08/argo_hu_a63ba12ce181b462.webp 1400w" src="https://percona.community/blog/2023/08/argo_hu_a63ba12ce181b462.webp" alt="dokc-game" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Robert created a public repository &lt;a href="https://github.com/Altinity/argocd-examples-clickhouse" target="_blank" rel="noopener noreferrer">Argocd-examples-clickhouse&lt;/a>, example of ArgoCD application definitions for ClickHouse analytic applications.&lt;/p>
&lt;p>In this project, you’ll find ArgoCD applications and instructions to stand up a full analytic stack based on ClickHouse in a Kubernetes cluster.&lt;/p>
&lt;p>Following suggestions from community members, a new Slack channel #topic-devops was created 🎉 .
This is a channel to talk about CI/CD integration, specific solutions like ArgoCD &amp; Flux, etc.&lt;/p>
&lt;p>To learn more about our meetings, join the &lt;a href="https://dok.community/" target="_blank" rel="noopener noreferrer">Data on Kubernetes&lt;/a>. An Open Community for Data on Kubernetes. We host weekly live meetups where technologists share their stories, wisdom, and practical advice for running data on Kubernetes.&lt;/p></content:encoded><author>Edith Puclla</author><category>DoK</category><category>Kubernetes</category><category>Operators</category><media:thumbnail url="https://percona.community/blog/2023/08/dok-game_hu_c4e7255be0a9266e.jpg"/><media:content url="https://percona.community/blog/2023/08/dok-game_hu_22de3756eda1290a.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.38 preview release</title><link>https://percona.community/blog/2023/06/30/preview-release/</link><guid>https://percona.community/blog/2023/06/30/preview-release/</guid><pubDate>Fri, 30 Jun 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.38 preview release Hello folks! Percona Monitoring and Management (PMM) 2.38 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-238-preview-release">Percona Monitoring and Management 2.38 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.38 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>To see the full list of changes, check out the &lt;a href="https://pmm-doc-pr-1081.onrender.com/release-notes/2.38.0.html" target="_blank" rel="noopener noreferrer">PMM 2.38 Release Notes&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker-installation">PMM server Docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Run PMM Server with Docker instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.38.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> To use the DBaaS functionality during the PMM preview release, add the following environment variable when starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.38.0-rc&lt;/code>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/el9/pmm2-client/pmm2-client-latest-5607.tar.gz" target="_blank" rel="noopener noreferrer">Download&lt;/a> the latest pmm2-client release candidate tarball for 2.38.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To install pmm2-client package, enable testing repository via Percona-release:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;ol start="3">
&lt;li>Install pmm2-client package for your OS via Package Manager.&lt;/li>
&lt;/ol>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-moitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Run PMM Server as a VM instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.38.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.38.0.ova file&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Run PMM Server hosted at AWS Marketplace instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-09895e9b605f14cbc&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us on the [Percona Community Forums](&lt;a href="https://forums.percona.com/]" target="_blank" rel="noopener noreferrer">https://forums.percona.com/]&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Release</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Percona University in Peru 2023</title><link>https://percona.community/blog/2023/06/20/percona-and-data-on-kubernetes-meetup/</link><guid>https://percona.community/blog/2023/06/20/percona-and-data-on-kubernetes-meetup/</guid><pubDate>Tue, 20 Jun 2023 00:00:00 UTC</pubDate><description>Peru is a country in South America home to a section of the Amazon rainforest and Machu Picchu ⛰️, an ancient Incan city high in the Andes mountains. Percona decided to hold the first event of Percona University 2023 in Lima, the capital of Peru, on June 10.</description><content:encoded>&lt;p>&lt;strong>Peru&lt;/strong> is a country in South America home to a section of the &lt;strong>Amazon&lt;/strong> rainforest and &lt;strong>Machu Picchu&lt;/strong> ⛰️, an ancient Incan city high in the Andes mountains. &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a> decided to hold the first event of &lt;strong>Percona University 2023 in Lima&lt;/strong>, the capital of Peru, on June 10.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/percona-university-is-back-in-business/" target="_blank" rel="noopener noreferrer">Percona University&lt;/a> is a series of free technical events organized by Percona in various cities around the world since 2013. &lt;strong>Percona&lt;/strong> uses these events to share its unbiased expertise on open source databases with the community, users, and organizations. The last &lt;a href="https://percona.community/events/percona-university-istanbul-2022/" target="_blank" rel="noopener noreferrer">Percona University was in 2022 in Istanbul, Turkey&lt;/a>.&lt;/p>
&lt;p>Something charming was designed for this first session of Percona University; there were all kinds of stickers and prizes at the end of the event.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1232" height="804" sizes="(max-width: 1232px) 100vw, 1232px" srcset="https://percona.community/blog/2023/06/pup-stikers-01_hu_463783fa1388a6e4.webp 768w, https://percona.community/blog/2023/06/pup-stikers-01_hu_dc636cbcfb0f95f1.webp 480w, https://percona.community/blog/2023/06/pup-stikers-01_hu_33a99d0100f904ed.webp 1232w" src="https://percona.community/blog/2023/06/pup-stikers-01_hu_33a99d0100f904ed.webp" alt="pup-stikers-01" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The talks were related to &lt;strong>open source databases&lt;/strong> and &lt;strong>kubernetes&lt;/strong>. Let’s summarize them:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Let’s start with &lt;a href="https://www.linkedin.com/in/peterzaitsev?miniProfileUrn=urn%3Ali%3Afs_miniProfile%3AACoAAAAQH8EBHFDyKi6meRnMSE5FNzSJilakYJQ&amp;lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3BdslLban%2BQgGG1jwigOsRaQ%3D%3D" target="_blank" rel="noopener noreferrer">Peter Zaitsev&lt;/a>, who talked about the &lt;a href="https://docs.google.com/presentation/d/12d27qQN0EIh3v-ssoZwzSR6ulXg_EcuO/edit#slide=id.p7" target="_blank" rel="noopener noreferrer">Cloud of Serfdom vs. the Cloud of Freedom&lt;/a>; why open source will win in the Cloud Age, spoke about the relationship between Cloud and Open Source, showing the historical changes in the impact of the &lt;strong>Cloud on Open Source&lt;/strong> and examining the current state of affairs, and advocating for a specific approach to using Cloud and Open Source together.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Peter’s&lt;/strong> next talk was about &lt;a href="https://docs.google.com/presentation/d/1AFjeTePOWYRyap1lmLa84kcfB0klBSQx/edit#slide=id.p1" target="_blank" rel="noopener noreferrer">17 reasons to migrate to MySQL 8&lt;/a>
MySQL 8 was different from previous major MySQL versions, as it underwent significant changes from its initial release in 2018 to the version in 2023. Many features were introduced through subsequent minor version releases. In the presentation, Peter focused on the most important Modern MySQL 8 features that had emerged since the initial release of &lt;strong>MySQL 8&lt;/strong>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-peter-02_hu_329d22bffefb71d.webp 768w, https://percona.community/blog/2023/06/pup-peter-02_hu_99791ae4c89a8a2d.webp 480w, https://percona.community/blog/2023/06/pup-peter-02_hu_2daab362da67d0f4.webp 1400w" src="https://percona.community/blog/2023/06/pup-peter-02_hu_2daab362da67d0f4.webp" alt="pup-peter-02" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>The next on the agenda was &lt;a href="https://www.linkedin.com/in/mvillegascuellar?miniProfileUrn=urn%3Ali%3Afs_miniProfile%3AACoAAAYosmwB_V8dLwgDO5dFwIsOtx_BSTIwYXA&amp;lipi=urn%3Ali%3Apage%3Ad_flagship3_search_srp_all%3BgaPel2l9SCeCz6vJLRN4Fw%3D%3D" target="_blank" rel="noopener noreferrer">Michael Villegas&lt;/a>. He talked about Useful tools from the &lt;a href="https://docs.google.com/presentation/d/1NX2c_DS9ussvc6VZmFT-4-wk28SIuKVs/edit#slide=id.p1" target="_blank" rel="noopener noreferrer">Percona Toolkit for DBAs&lt;/a>. This talk was aimed at DBAs and developers of any level of experience responsible for MySQL database administration. They learned how to use some very useful tools within the &lt;a href="https://www.percona.com/software/database-tools/percona-toolkit" target="_blank" rel="noopener noreferrer">Percona Toolkit&lt;/a> to solve common problems within their databases. Michael showed these tools allowed them to save time and effort in resolving these issues.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-michael-03_hu_ca499d69087cf5b2.webp 768w, https://percona.community/blog/2023/06/pup-michael-03_hu_d06831951e47f5e7.webp 480w, https://percona.community/blog/2023/06/pup-michael-03_hu_c03b34d225a3913d.webp 1400w" src="https://percona.community/blog/2023/06/pup-michael-03_hu_c03b34d225a3913d.webp" alt="pup-michael-03" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After the following talk, we had a coffee break provided by Percona, it was an opportunity to chat with Peter and network with other attendees.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4030" height="2830" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-breakfast-04_hu_16dc7322a3025b68.webp 768w, https://percona.community/blog/2023/06/pup-breakfast-04_hu_302354afadb7f1a.webp 480w, https://percona.community/blog/2023/06/pup-breakfast-04_hu_f7fa66a691214b83.webp 1400w" src="https://percona.community/blog/2023/06/pup-breakfast-04_hu_f7fa66a691214b83.webp" alt="pup-breakfast-04" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The next talk was by &lt;strong>Peter Zaitsev&lt;/strong> about &lt;strong>Advanced MySQL optimization and troubleshooting using PMM&lt;/strong>. Optimizing MySQL performance and troubleshooting MySQL problems are two of the most critical and challenging tasks for MySQL DBAs. The databases power their applications need to handle changing traffic workloads while remaining responsive and stable, ensuring an excellent user experience. Additionally, DBAs are expected to find cost-efficient solutions to these issues. In the presentation, Peter Zaitsev showed advanced options of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/index.html" target="_blank" rel="noopener noreferrer">PMM version 2&lt;/a>, which allowed DBAs to address these challenges.
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-peter-05_hu_1c718c0097b9f8c4.webp 768w, https://percona.community/blog/2023/06/pup-peter-05_hu_674eda397467c5a3.webp 480w, https://percona.community/blog/2023/06/pup-peter-05_hu_9713c9a783996826.webp 1400w" src="https://percona.community/blog/2023/06/pup-peter-05_hu_9713c9a783996826.webp" alt="pup-peter-05" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The next talk was for &lt;a href="https://www.linkedin.com/in/edithpuclla/" target="_blank" rel="noopener noreferrer">Edith Puclla&lt;/a> (me). I did an &lt;a href="https://docs.google.com/presentation/d/1URi6oNC3fZKd2mCAZ3CGZ_CTkAkzIHWW/edit#slide=id.p1" target="_blank" rel="noopener noreferrer">Introduction to Kubernetes Operators&lt;/a>. I provided a simplified overview of &lt;strong>Kubernetes Operators&lt;/strong>, focusing on making the concept understandable for those new to the subject. I started explaining Kubernetes and why they are relevant in managing applications, then we go for Kubernetes Operators example, the reason behind that, and the benefits of using Operators.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-edith-06_hu_f9788ca3088f72f6.webp 768w, https://percona.community/blog/2023/06/pup-edith-06_hu_ad631dd6797fd048.webp 480w, https://percona.community/blog/2023/06/pup-edith-06_hu_b7ed5d80ba899cbd.webp 1400w" src="https://percona.community/blog/2023/06/pup-edith-06_hu_b7ed5d80ba899cbd.webp" alt="pup-edith-06" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our last talk was about &lt;a href="https://docs.google.com/presentation/d/10mzZu-N_mv_4zpD3-6LVXN0Lv01ws7tn/edit#slide=id.p1" target="_blank" rel="noopener noreferrer">Deep Dive Into Query Performance&lt;/a> by &lt;strong>Peter Zaitsev&lt;/strong>. In this presentation, Peter explored this seemingly simple aspect of working with databases in detail. Peter answered questions like when you should focus on tuning specific queries or when it is better to focus on tuning the database (or just getting a bigger box). Peter also showed other ways to minimize user facing response time, such as parallel queries, asynchronous queries, queueing complex work, and as often misunderstood response time killers such as overloaded networks, stolen CPU, and even limits imposed by this pesky speed of light.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3935" height="2719" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-peter-07_hu_217dc2a2b418004c.webp 768w, https://percona.community/blog/2023/06/pup-peter-07_hu_3870395771435422.webp 480w, https://percona.community/blog/2023/06/pup-peter-07_hu_ad38ad71baf5252b.webp 1400w" src="https://percona.community/blog/2023/06/pup-peter-07_hu_ad38ad71baf5252b.webp" alt="pup-peter-07" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The event was well received. Many graduates, professionals, students, and open source enthusiasts attended the event.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2862" height="2932" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-team-08_hu_ee260c6c8f36a878.webp 768w, https://percona.community/blog/2023/06/pup-team-08_hu_2cf38acf6217cfc8.webp 480w, https://percona.community/blog/2023/06/pup-team-08_hu_9d2c853f6e5cdff2.webp 1400w" src="https://percona.community/blog/2023/06/pup-team-08_hu_9d2c853f6e5cdff2.webp" alt="pup-team-08" loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-public-09_hu_9e97cdbd98371f59.webp 768w, https://percona.community/blog/2023/06/pup-public-09_hu_2935dc2f6f6e0329.webp 480w, https://percona.community/blog/2023/06/pup-public-09_hu_19afa78204b8f40c.webp 1400w" src="https://percona.community/blog/2023/06/pup-public-09_hu_19afa78204b8f40c.webp" alt="pup-public-09" loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-all-10_hu_f754a7caa5ac3efe.webp 768w, https://percona.community/blog/2023/06/pup-all-10_hu_79528355a1e4d684.webp 480w, https://percona.community/blog/2023/06/pup-all-10_hu_a992048a838ab151.webp 1400w" src="https://percona.community/blog/2023/06/pup-all-10_hu_a992048a838ab151.webp" alt="pup-all-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And also nice to share moments with Peter and his fans.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-lunch-11_hu_5084de035e86b96a.webp 768w, https://percona.community/blog/2023/06/pup-lunch-11_hu_7d68d79de5ea3a4c.webp 480w, https://percona.community/blog/2023/06/pup-lunch-11_hu_d90ca3c047788823.webp 1400w" src="https://percona.community/blog/2023/06/pup-lunch-11_hu_d90ca3c047788823.webp" alt="pup-lunch-11" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We also thank &lt;a href="https://www.ue.edu.pe/" target="_blank" rel="noopener noreferrer">ESAN University&lt;/a> for providing us with the venue for the event.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="3024" height="4032" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/pup-team-12_hu_b965248805564b93.webp 768w, https://percona.community/blog/2023/06/pup-team-12_hu_5fee4179b753b96a.webp 480w, https://percona.community/blog/2023/06/pup-team-12_hu_33beb218f7b8cd50.webp 1400w" src="https://percona.community/blog/2023/06/pup-team-12_hu_33beb218f7b8cd50.webp" alt="pup-team-12" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Don’t miss our next &lt;a href="https://learn.percona.com/percona-university-istanbul-2022" target="_blank" rel="noopener noreferrer">Percona University event, in Istanbul&lt;/a>!&lt;/p>
&lt;p>See you at &lt;strong>Percona University Peru in 2024&lt;/strong>!&lt;/p></content:encoded><author>Edith Puclla</author><category>Events</category><category>Kubernetes</category><category>Toolkit</category><category>Operators</category><category>PMM</category><media:thumbnail url="https://percona.community/blog/2023/06/pup-all-10_hu_96ec1fce07252097.jpeg"/><media:content url="https://percona.community/blog/2023/06/pup-all-10_hu_4e47862a67581e0e.jpeg" medium="image"/></item><item><title>Data on Kubernetes Meetup May 23</title><link>https://percona.community/blog/2023/06/01/percona-and-data-on-kubernetes-meetup/</link><guid>https://percona.community/blog/2023/06/01/percona-and-data-on-kubernetes-meetup/</guid><pubDate>Thu, 01 Jun 2023 00:00:00 UTC</pubDate><description>Percona has started to participate in Data on Kubernetes (DoK) meetings about Kubernetes Operators. These meetings are an initiative of DoK meetups that spotlight DoK case studies. In this blog post series, I will summarize the topics covered in each meeting.</description><content:encoded>&lt;p>&lt;strong>Percona&lt;/strong> has started to participate in &lt;strong>Data on Kubernetes&lt;/strong> (DoK) meetings about &lt;strong>Kubernetes Operators&lt;/strong>.
These meetings are an initiative of DoK meetups that spotlight DoK case studies. In this blog post series, I will summarize the topics covered in each meeting.&lt;/p>
&lt;p>On May 23, very interesting topics were discussed on the agenda. Let’s begin to summarize it.&lt;/p>
&lt;p>We start with a new project proposal, which is called: &lt;a href="https://docs.google.com/document/d/1CJeFtNpDSyaPoPWvimwMFt5s1g2Zj2Ppg_DJX7nVurk/edit#" target="_blank" rel="noopener noreferrer">Distributed Systems Operator Interface (DSOI)&lt;/a>. It is proposed by &lt;strong>Adheip Singh&lt;/strong> from DataInfra, &lt;strong>Nitish Tiwari&lt;/strong> from Parseable, and &lt;strong>Itamar Marom&lt;/strong> from AppsFlyer.&lt;/p>
&lt;p>This project is a set of best practices for building Kubernetes operators for distributed systems. The spec defines standard practices that can help define custom resources (CR). It consists of Kubernetes-native &lt;strong>CRDs&lt;/strong> and specs and is not bound to any specific application. There are already two operators built using this set of practices:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/parseablehq/operator" target="_blank" rel="noopener noreferrer">Parseable Kubernetes Operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/datainfrahq/pinot-operator" target="_blank" rel="noopener noreferrer">Control Plane For Apache Pinot&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>If you want to contribute, send proposals, join &lt;a href="https://launchpass.com/datainfra-workspace" target="_blank" rel="noopener noreferrer">datainfra-workspace&lt;/a> or raise bugs in the GitHub repository of &lt;a href="https://github.com/datainfrahq/dsoi-spec/issues" target="_blank" rel="noopener noreferrer">DSOI&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1762" height="808" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/dok-datainfra_hu_8c44d22b253fd108.webp 768w, https://percona.community/blog/2023/06/dok-datainfra_hu_e43ad88b505cc21.webp 480w, https://percona.community/blog/2023/06/dok-datainfra_hu_3f3b98a863d20d03.webp 1400w" src="https://percona.community/blog/2023/06/dok-datainfra_hu_3f3b98a863d20d03.webp" alt="Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As the second item on the agenda, we have an update about the &lt;a href="https://docs.google.com/document/d/1tbm44jC1qf6kAf9qje5V-UhaXG-AlGud9nhMaoPN6mU/edit#heading=h.fjdgqyupbu03" target="_blank" rel="noopener noreferrer">DoK Operator SIG Project Proposal - Security &amp; Hardening Guide&lt;/a>. This project is proposed by &lt;strong>Robert Hodges&lt;/strong>, Altinity Inc.&lt;/p>
&lt;p>This project is a guide to establishing a baseline for secure data management on Kubernetes by fortifying the database operators. The guide aims to identify the typical attack surfaces that exist for databases running on Kubernetes. &lt;strong>It will establish a collection of best practices for enhancing their security through the utilization of operators&lt;/strong>.&lt;/p>
&lt;p>Robert mentioned that he connected to TAG Security, which in turn led to a link to BadRobot, which is a scanner that checks operators for excessive privileges. Also, Robert presented to DoK Bay Area last week to introduce the problem of operator security.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2044" height="1144" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/06/dok-security-hardering_hu_a28bf005487a1970.webp 768w, https://percona.community/blog/2023/06/dok-security-hardering_hu_50cfee5639ba0898.webp 480w, https://percona.community/blog/2023/06/dok-security-hardering_hu_ad00c9d8e1ad7781.webp 1400w" src="https://percona.community/blog/2023/06/dok-security-hardering_hu_ad00c9d8e1ad7781.webp" alt="Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For the Operator security &amp; hardening guide, we can raise issues on &lt;a href="https://github.com/dokc/sig-operator" target="_blank" rel="noopener noreferrer">sig-operator&lt;/a>. They are currently seeking volunteers and contributors for their project; Find &lt;a href="https://github.com/dokc/sig-operator/tree/main/operator-security-hardening" target="_blank" rel="noopener noreferrer">operator-security-hardening&lt;/a> project on GitHub, or feel free to write Robert Hodges &lt;strong>&lt;a href="mailto:rhodges@altinity.com">rhodges@altinity.com&lt;/a>&lt;/strong>.&lt;/p>
&lt;p>Finally, we have an update of the Operator Feature Matrix (OFM)&lt;/p>
&lt;p>The &lt;strong>Operator Feature Matrix (OFM)&lt;/strong> is a project from the Data on Kubernetes Community to create a standardized and vendor-neutral feature matrix for various Kubernetes operators that manage stateful workloads. This project is proposed by &lt;strong>Alvaro Hernandez&lt;/strong>, and it is definitely a good project to contribute if you are looking to improve the end-user experience with the use of workloads in Kubernetes.&lt;/p>
&lt;p>&lt;strong>CloudNativePG&lt;/strong> project sent feedback to improve OFM. CloudNativePG is the Kubernetes operator that covers the full lifecycle of a highly available PostgreSQL database cluster. Planning to create a website for end-user adoption&lt;/p>
&lt;p>There are other (non-Postgres) technologies, like Apache Druid, jumping on OFM. This is a work in progress.&lt;/p>
&lt;p>The end of June is being considered for a 1.0 freeze, before which it is required to get as much feedback as possible.
If you are interested, feedback can be as simple as opening an issue to discuss something; or sending a PR requesting improvements (or both). Feel free to do it on &lt;a href="https://github.com/dokc/operator-feature-matrix" target="_blank" rel="noopener noreferrer">OFM GitHub Repo&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>DoK</category><category>Opensource</category><category>CNCF</category><category>Kubernetes</category><category>Operators</category><media:thumbnail url="https://percona.community/blog/2023/06/dok-intro_hu_551fc8233fc2d418.jpg"/><media:content url="https://percona.community/blog/2023/06/dok-intro_hu_9e64bcec089fa010.jpg" medium="image"/></item><item><title>​What experts said at Kubecon about Data on Kubernetes</title><link>https://percona.community/blog/2023/05/31/what-experts-said-at-kubecon-about-data-on-kubernetes/</link><guid>https://percona.community/blog/2023/05/31/what-experts-said-at-kubecon-about-data-on-kubernetes/</guid><pubDate>Wed, 31 May 2023 00:00:00 UTC</pubDate><description>Melissa Logan, managing director of Data on Kubernetes (DoK), led one of the best panels I’ve been to at a conference at Kubecon EU in Amsterdam about challenges with and the state of the art of running databases on Kubernetes.</description><content:encoded>&lt;p>&lt;strong>Melissa Logan&lt;/strong>, managing director of &lt;strong>Data on Kubernetes&lt;/strong> (DoK), led one of the &lt;a href="https://www.youtube.com/watch?v=TmDdkBPW_hI&amp;t=313s" target="_blank" rel="noopener noreferrer">best panels I’ve been to at a conference at Kubecon EU&lt;/a> in Amsterdam about challenges with and the state of the art of running databases on Kubernetes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1064" height="552" sizes="(max-width: 1064px) 100vw, 1064px" srcset="https://percona.community/blog/2023/05/01-pd-intro_hu_350e3e3028232c2b.webp 768w, https://percona.community/blog/2023/05/01-pd-intro_hu_1e71a08f4535e62f.webp 480w, https://percona.community/blog/2023/05/01-pd-intro_hu_be9ed157aac5e042.webp 1064w" src="https://percona.community/blog/2023/05/01-pd-intro_hu_be9ed157aac5e042.webp" alt="Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This panel united the &lt;strong>Data on Kubernetes Community Operator SIG&lt;/strong> and &lt;strong>Kubernetes Storage SIG&lt;/strong> to discuss key features of Kubernetes database operators. &lt;strong>Xing Yang&lt;/strong> from VMware, &lt;strong>Sergey Pronin&lt;/strong> from Percona, and &lt;strong>Álvaro Hernández&lt;/strong> from OnGres came together to discuss what works, what doesn’t, and where the industry is going. They also presented a feature matrix to help end users compare many database Operators.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1370" height="720" sizes="(max-width: 1370px) 100vw, 1370px" srcset="https://percona.community/blog/2023/05/02-pd-panel-discution_hu_447f05c067ff5034.webp 768w, https://percona.community/blog/2023/05/02-pd-panel-discution_hu_fbd6de061766ccfa.webp 480w, https://percona.community/blog/2023/05/02-pd-panel-discution_hu_f2248e0d4cc391dc.webp 1370w" src="https://percona.community/blog/2023/05/02-pd-panel-discution_hu_f2248e0d4cc391dc.webp" alt="Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you are new to the topic of Kubernetes Operators, I wrote a blog post about &lt;a href="https://percona.community/blog/2022/10/13/learning-kubernetes-operators-with-percona-operator-for-mongodb/" target="_blank" rel="noopener noreferrer">Kubernetes Operators in a nutshell&lt;/a>, you can read the first part of this article.&lt;/p>
&lt;p>Let’s start by summarizing the challenges the panelists mentioned when running &lt;strong>stateful&lt;/strong> applications on &lt;strong>Kubernetes&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Some Operators have certain limitations, and there are security concerns. Database users always think about data encryption: is the data safe? What happens if the node goes down? What happens if we lose the storage?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The Operator model is very extensible and flexible, which is great, but on the other hand, there are so many Operators, and it becomes a challenge to choose the right one for our use cases.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>People who are developing Operators also find challenges because every database has its native way of doing backups, but if you want to support more than one type of database, then it’s more challenging to find a generic way.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="does-the-framework-capture-what-is-needed-for-data-workloads-well">Does the framework capture what is needed for data workloads well?&lt;/h2>
&lt;ul>
&lt;li>There is a capability model for Operators that classify them into five levels. There is room for improvement in this model. It can be improved to build test compatibility and more objective measures of these capability levels if you look at level five, the top one for data workloads.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="952" height="405" sizes="(max-width: 952px) 100vw, 952px" srcset="https://percona.community/blog/2023/05/03-pd-capability-models_hu_e8fc37d50d7a6247.webp 768w, https://percona.community/blog/2023/05/03-pd-capability-models_hu_8726f384cd172c80.webp 480w, https://percona.community/blog/2023/05/03-pd-capability-models_hu_5ceb2c05567b9c2b.webp 952w" src="https://percona.community/blog/2023/05/03-pd-capability-models_hu_5ceb2c05567b9c2b.webp" alt="Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Security is the number one criterion that people use to evaluate Operators. How are they addressing security in Kubernetes Operators?&lt;/li>
&lt;li>Users want to ensure that the Operator does not get a lot of privileges or does not interfere with other tenants in the Kubernetes cluster.&lt;/li>
&lt;li>Now users are looking for more sophisticated ways with their existing security key-value storage. They can be sure they are safe.&lt;/li>
&lt;li>The framework should provide ransomware protection, so when you back up your databases, you also want to have one immutable copy to provide your protection and recover from that.&lt;/li>
&lt;/ul>
&lt;h2 id="now-lets-talk-about-solutions">Now Let’s talk about solutions&lt;/h2>
&lt;p>&lt;strong>DoK&lt;/strong> started an &lt;strong>Operator Special Interest Group&lt;/strong> (SIG), and community members have been meeting to discuss how as a group and as an industry, to collaborate to come up with solutions for some of the challenges end users face. The Operator SIG works with the Storage Technical Advisory Group (TAG), Storage SIG, and Security SIG.&lt;/p>
&lt;p>According to data on Kubernetes’s (DoK) &lt;a href="https://dok.community/wp-content/uploads/2022/10/DoK_Report_2022.pdf" target="_blank" rel="noopener noreferrer">first report&lt;/a>, 70% of responses are running workloads in production. More data workloads are running on Kubernetes, so it is essential to know what works well. Sharing knowledge and leveraging that expertise is vital at this stage.&lt;/p>
&lt;p>There are things that SIG Operators detail in a &lt;a href="https://docs.google.com/document/d/1Uyk5qQ4KhpI-YnLdG72V66dO9Hxv_kqTK_CrMHS9EFc/edit#heading=h.nxcx7r52ocev" target="_blank" rel="noopener noreferrer">document&lt;/a>, like common patterns and features used when running databases on Kubernetes, best practices, the criteria for running a good operator, why observability is so important in the cloud-native environment and security.&lt;/p>
&lt;p>The &lt;strong>Operator Feature Matrix&lt;/strong> is a big initiative to help end users find operators based on different criteria to choose what they need. It is a project to compare different Operators. They are starting with database Operators, and one project is already defined: &lt;a href="https://github.com/dokc/operator-feature-matrix/tree/main/postgres" target="_blank" rel="noopener noreferrer">Postgres Operator Feature Matrix&lt;/a>. Feel free to contribute to &lt;a href="https://github.com/dokc/operator-feature-matrix" target="_blank" rel="noopener noreferrer">OFM&lt;/a>.&lt;/p>
&lt;p>New to Kubernetes Operators and databases? Check out &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona’s Operators&lt;/a> for MySQL, PostgreSQL, and MongoDB.&lt;/p></content:encoded><author>Edith Puclla</author><category>Kubeconeu</category><category>Opensource</category><category>CNCF</category><category>Kubernetes</category><category>DoK</category><category>Operators</category><media:thumbnail url="https://percona.community/blog/2023/05/01-pd-intro_hu_19ef74926ec419b2.jpeg"/><media:content url="https://percona.community/blog/2023/05/01-pd-intro_hu_c928a8c7630a9410.jpeg" medium="image"/></item><item><title>Easy Way to Start Contributing to Open Source With PMM Documentation</title><link>https://percona.community/blog/2023/05/18/easy-way-to-start-contributing-to-open-source-with-pmm-documentation/</link><guid>https://percona.community/blog/2023/05/18/easy-way-to-start-contributing-to-open-source-with-pmm-documentation/</guid><pubDate>Thu, 18 May 2023 00:00:00 UTC</pubDate><description>If you are a user of Percona Monitoring and Management and noticed any typo or inaccurate information in its documentation, you can easily correct it yourself in the repository following detailed instructions in README.md. But if you are not experienced in open source contributions, you may still feel uneasy about following those steps. This post is for you! We will walk through the main steps with pictures and explanations.</description><content:encoded>&lt;p>If you are a user of Percona Monitoring and Management and noticed any typo or inaccurate information in its &lt;a href="https://docs.percona.com/percona-monitoring-and-management/index.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>, you can easily correct it yourself in the &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">repository&lt;/a> following detailed instructions in &lt;a href="https://github.com/percona/pmm-doc#readme" target="_blank" rel="noopener noreferrer">README.md&lt;/a>. But if you are not experienced in open source contributions, you may still feel uneasy about following those steps. This post is for you! We will walk through the main steps with pictures and explanations.&lt;/p>
&lt;h2 id="create-a-fork">Create a Fork&lt;/h2>
&lt;p>First, you need to create a fork from the main repository to your account. In the top-right corner of the page, click &lt;strong>Fork - Create a new fork&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/contribution2_hu_d8d1f4463ecbaf56.webp 768w, https://percona.community/blog/2023/05/contribution2_hu_3fd230138cf29d36.webp 480w, https://percona.community/blog/2023/05/contribution2_hu_699fa669e3aae41d.webp 1400w" src="https://percona.community/blog/2023/05/contribution2_hu_699fa669e3aae41d.webp" alt="Contribution" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="build-documentation-with-docker">Build Documentation With Docker&lt;/h2>
&lt;p>The easiest way is to build documentation with Docker. If you don’t have it installed, download it from the Docker official website and follow the instructions. The process of installation is quick, and it is no more difficult than the installation of any other app.&lt;/p>
&lt;p>Open your fork on GitHub and clone that repository to your local environment.&lt;/p>
&lt;p>&lt;code>git clone git@github.com:{user-name}/pmm-doc.git&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/contribution1_hu_140ea5b8c7af5639.webp 768w, https://percona.community/blog/2023/05/contribution1_hu_257e08913504c74e.webp 480w, https://percona.community/blog/2023/05/contribution1_hu_2789dc4991b139ad.webp 1400w" src="https://percona.community/blog/2023/05/contribution1_hu_2789dc4991b139ad.webp" alt="Contribution" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Change directory to &lt;strong>pmm-doc&lt;/strong>.&lt;/p>
&lt;p>&lt;code>cd pmm-doc&lt;/code>&lt;/p>
&lt;p>To check how our edits will look like, we need to build documentation for live previewing. Run:&lt;/p>
&lt;p>&lt;code>docker run --rm -v $(pwd):/docs -p 8000:8000 perconalab/pmm-doc-md mkdocs serve --dev-addr=0.0.0.0:8000&lt;/code>&lt;/p>
&lt;p>Wait until you see &lt;code>INFO - Start detecting changes&lt;/code>. When the documentation is ready to work with, it will be available at &lt;a href="http://0.0.0.0:8000/" target="_blank" rel="noopener noreferrer">http://0.0.0.0:8000&lt;/a> in your browser, and it will reflect all changes that you make locally.&lt;/p>
&lt;h2 id="make-changes">Make Changes&lt;/h2>
&lt;p>In a new Terminal tab, create a new branch and make your changes. Save them, create a commit, and push it to your fork.&lt;/p>
&lt;p>Create a pull request to the main repository. You will also need to sign the CLA, so we could merge your changes.&lt;/p>
&lt;p>You did it! Congratulations! Now wait for the feedback from the Percona team. If there is no problem with your PR, it will be merged into the main repository.&lt;/p>
&lt;h2 id="next-steps">Next Steps&lt;/h2>
&lt;p>To make further changes, you need to keep your repository up-to-date with the upstream one. There are several ways to do it. You can find the information &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork" target="_blank" rel="noopener noreferrer">here&lt;/a>. The simplest way is to do it using the GitHub interface. Just click on &lt;strong>Sync fork&lt;/strong> and then &lt;strong>Update branch&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1036" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/contribution3_hu_8b7cb39d8409beca.webp 768w, https://percona.community/blog/2023/05/contribution3_hu_51e78b68952dfaae.webp 480w, https://percona.community/blog/2023/05/contribution3_hu_d6aa684679197939.webp 1400w" src="https://percona.community/blog/2023/05/contribution3_hu_d6aa684679197939.webp" alt="Contribution" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After that, you will be able to update your local repository with &lt;code>git pull&lt;/code> command.&lt;/p>
&lt;p>If you face any problems with contributions to Percona repositories, don’t hesitate to contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> or ask your question on the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Forum&lt;/a>.&lt;/p></content:encoded><category>Opensource</category><category>Documentation</category><media:thumbnail url="https://percona.community/blog/2023/05/PMM-Doc-Contribute_hu_405740408eff0264.jpg"/><media:content url="https://percona.community/blog/2023/05/PMM-Doc-Contribute_hu_7c2bbccc235458aa.jpg" medium="image"/></item><item><title>​My Experience at Kubecon Europe in Amsterdam</title><link>https://percona.community/blog/2023/05/11/experience-at-kubecon-europe-in-amsterdam/</link><guid>https://percona.community/blog/2023/05/11/experience-at-kubecon-europe-in-amsterdam/</guid><pubDate>Thu, 11 May 2023 00:00:00 UTC</pubDate><description>Kubecon is the most significant event focused on the Kubernetes ecosystem. It takes place once a year in North America, Europe, and Asia. It is a perfect opportunity to learn from experts, meet friends, grow your network, and attend talks at a varied technical level and meetings focused on CNCF communities. This time I attended Kubecon in Amsterdam. The theme for this version of Kubecon was: community-in-bloom because we are still healing from COVID, and people are getting back to feeling comfortable participating in events.</description><content:encoded>&lt;p>&lt;strong>Kubecon&lt;/strong> is the most significant event focused on the &lt;strong>Kubernetes&lt;/strong> ecosystem. It takes place once a year in North America, Europe, and Asia. It is a perfect opportunity to learn from experts, meet friends, grow your network, and attend talks at a varied technical level and meetings focused on CNCF communities.
This time I attended Kubecon in Amsterdam. The theme for this version of Kubecon was: &lt;strong>community-in-bloom&lt;/strong> because we are still healing from COVID, and people are getting back to feeling comfortable participating in events.&lt;/p>
&lt;p>It is not my first time attending &lt;strong>Kubecon&lt;/strong>; this is my fourth time! What was different this time was that &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a>, the company I work for, sponsored Kubecon. This means we also had a booth at the event to share what we do at Percona.&lt;/p>
&lt;p>Yessss!! Percona had a booth in Kubecon, Amsterdam.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1634" height="750" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/01-percona-kubecon_hu_c6168bc00c0386bc.webp 768w, https://percona.community/blog/2023/05/01-percona-kubecon_hu_d080d2fe57f1e019.webp 480w, https://percona.community/blog/2023/05/01-percona-kubecon_hu_e5fcaca10c23f69d.webp 1400w" src="https://percona.community/blog/2023/05/01-percona-kubecon_hu_e5fcaca10c23f69d.webp" alt="Percona At Kubecon" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Percona is a 100% &lt;strong>remote&lt;/strong> company, and &lt;strong>Kubecon&lt;/strong> was the opportunity to meet part of the team I work with daily.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1536" height="2048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/02-percona-team_hu_be0254d595925804.webp 768w, https://percona.community/blog/2023/05/02-percona-team_hu_906bf9411bcd9295.webp 480w, https://percona.community/blog/2023/05/02-percona-team_hu_506336976c035887.webp 1400w" src="https://percona.community/blog/2023/05/02-percona-team_hu_506336976c035887.webp" alt="Percona Team" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We started by meeting with friends in one of the most popular places in &lt;strong>Amsterdam&lt;/strong>. I met people from different tech communities and &lt;strong>CNCF ambassadors&lt;/strong>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_cc1c6392c367b778.webp 768w, https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_fa40783f9567a2.webp 480w, https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_7928f1b03a6ef44a.webp 1400w" src="https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_7928f1b03a6ef44a.webp" alt="Kubecon Happy Hour" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>If you are in Amsterdam and don’t ride a bicycle, it is an incomplete experience&lt;/em>. We rode a bike to the convention center and collected the event badge.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1534" height="786" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/04-bicicle_hu_4201476bba152935.webp 768w, https://percona.community/blog/2023/05/04-bicicle_hu_a74bb84189644298.webp 480w, https://percona.community/blog/2023/05/04-bicicle_hu_69b895bc5b754022.webp 1400w" src="https://percona.community/blog/2023/05/04-bicicle_hu_69b895bc5b754022.webp" alt="Amsterdam Bicycle" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After that, there were three intense days, many activities, meetings, and sessions.&lt;/p>
&lt;h2 id="community">Community&lt;/h2>
&lt;p>I met with several members of the &lt;strong>Docker&lt;/strong> and &lt;strong>CNCF&lt;/strong> communities. This is the meeting of the Docker captains and various members of the Docker team. Such an amazing experience to know them in person.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1170" height="878" sizes="(max-width: 1170px) 100vw, 1170px" srcset="https://percona.community/blog/2023/05/05-docker-community_hu_6cce8c7f50445d74.webp 768w, https://percona.community/blog/2023/05/05-docker-community_hu_d0cc17d40fc89f4c.webp 480w, https://percona.community/blog/2023/05/05-docker-community_hu_f7f55513073e45fe.webp 1170w" src="https://percona.community/blog/2023/05/05-docker-community_hu_f7f55513073e45fe.webp" alt="Docker Captains" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And this is the group of photos that all the CNCF ambassadors had at Kubecon. There are 155 CNCF ambassadors around the world and contributors and advocates of the CNCF ecosystem.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1567" height="1045" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_687a5d881a9f98d5.webp 768w, https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_6d52f0a6002ffc78.webp 480w, https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_df2ff8b524bf0f38.webp 1400w" src="https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_df2ff8b524bf0f38.webp" alt="CNCF ambassadors breakfast" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="lightning-talks">Lightning Talks&lt;/h2>
&lt;p>I attended a large part of the lightning sessions, which were very inspiring. Each speaker has less than 5 minutes to explain a specific topic.&lt;/p>
&lt;p>&lt;strong>Kevin Patrick&lt;/strong> talked about the &lt;a href="https://www.youtube.com/watch?v=eAoC1ordaXQ" target="_blank" rel="noopener noreferrer">Armada as a Sandbox project in the CNCF&lt;/a>, in which the principal goal is enabling batch processing across multiple Kubernetes clusters. Kevin made an introduction to Armada and showed the integration with &lt;a href="https://airflow.apache.org/" target="_blank" rel="noopener noreferrer">Apache Airflow&lt;/a>.&lt;/p>
&lt;p>The next talk was about &lt;a href="https://www.youtube.com/watch?v=BDA7atvmnV4" target="_blank" rel="noopener noreferrer">The CNCF Board Game Rules&lt;/a>, where &lt;strong>Peter O’Neill&lt;/strong> made an abstract about the world of the CNCF and imagined it as a role-playing game (RPG) board game. It was pretty nice for Peter to show us the adventure of CNCF with games.&lt;/p>
&lt;p>This was one of my favorite lightning talks: [A Beginners Guide to Conference Speaking] (&lt;a href="https://www.youtube.com/watch?v=jCz9QPrJ6Eo%29with" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=jCz9QPrJ6Eo)with&lt;/a> &lt;strong>Paula Kennedy&lt;/strong>. She showed a friendly way to prepare proposals for CFPs and advice that will help us through the process.&lt;/p>
&lt;p>Another good lightning session was about the &lt;a href="https://www.youtube.com/watch?v=Wn0S6CTXGS4" target="_blank" rel="noopener noreferrer">Power-Aware Scheduling in Kubernetes&lt;/a> with &lt;strong>Yuan Chen&lt;/strong> from Apple. In this talk, Yan gave an overview of a new scheduler feature to support power-aware scheduling in Kubernetes and how it can help safely increase server hardware and data center infrastructure size and improve resource utilization and workload reliability for Kubernetes clusters.&lt;/p>
&lt;p>Another of my favorites was &lt;a href="https://www.youtube.com/watch?v=Kp6GQjZixPE" target="_blank" rel="noopener noreferrer">Talking to Kubernetes with Rust&lt;/a> with &lt;strong>James Laverack&lt;/strong>, where he showed how to interact with Kubernetes in Rust.&lt;/p>
&lt;h2 id="kubernetes-operators-panel-discussion">Kubernetes Operators Panel Discussion&lt;/h2>
&lt;p>I also attended a &lt;strong>Panel Discussion about Kubernetes Operators&lt;/strong>.
In this panel discussion, &lt;strong>Xing Yang&lt;/strong>, &lt;strong>Melissa Logan&lt;/strong>, &lt;strong>Sergey Pronin&lt;/strong>, and &lt;strong>Alvaro Hernandez&lt;/strong> talked about the challenges that final users have when running data workloads in Kubernetes Operators. They also shared about the need to fulfill the process of data workloads.
Check out this fantastic &lt;a href="https://www.youtube.com/watch?v=TmDdkBPW_hI&amp;list=PLj6h78yzYM2PyrvCoOii4rAopBswfz1p7&amp;index=184" target="_blank" rel="noopener noreferrer">talk&lt;/a> and learn more about &lt;strong>Kubernetes Operators&lt;/strong>.
A very curious and interesting fact is that most of the assistants use data workload with Kubernetes Operators&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1600" height="1200" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/07-kuberentes-operators_hu_5ce6f20784553f40.webp 768w, https://percona.community/blog/2023/05/07-kuberentes-operators_hu_f3c92263e806fbc7.webp 480w, https://percona.community/blog/2023/05/07-kuberentes-operators_hu_290dd1b481a03536.webp 1400w" src="https://percona.community/blog/2023/05/07-kuberentes-operators_hu_290dd1b481a03536.webp" alt="Kubernetes Operator Panel Discussion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="ebpf">eBPF&lt;/h2>
&lt;p>The last talk I attended was an &lt;strong>eBPF&lt;/strong> talk with &lt;strong>Liz Rice&lt;/strong> (Chief Open Source Officer, Savant)
In this talk, Liz shows a demo about how Cilium and its ClusterMesh feature can take care of many aspects of connectivity across multiple clusters in a cloud-agnostic way. Check this &lt;a href="https://www.youtube.com/watch?v=fJiuqRY5Oi4&amp;t=22s" target="_blank" rel="noopener noreferrer">talk in CNCF YouTube Channel&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/08-ebpf_hu_2021330077d4bab0.webp 768w, https://percona.community/blog/2023/05/08-ebpf_hu_d5b35b4b30658fd4.webp 480w, https://percona.community/blog/2023/05/08-ebpf_hu_9b17ee9afdbe229b.webp 1400w" src="https://percona.community/blog/2023/05/08-ebpf_hu_9b17ee9afdbe229b.webp" alt="Kubecon eBPF" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="clossign-kubecon-amsterdam">Clossign Kubecon Amsterdam&lt;/h2>
&lt;p>Finally, in Percona, we closed the event with a raffle to take home an Atari’; the expectations were relatively high and very fun, and many Percona lovers came to participate in the raffle.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4032" height="3024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/05/09-raffle_hu_fe63422f36d067d0.webp 768w, https://percona.community/blog/2023/05/09-raffle_hu_e9da7da946e734f4.webp 480w, https://percona.community/blog/2023/05/09-raffle_hu_60b73be76b6fc2c.webp 1400w" src="https://percona.community/blog/2023/05/09-raffle_hu_60b73be76b6fc2c.webp" alt="Percona Raffle" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You can check in which &lt;a href="https://www.percona.com/events" target="_blank" rel="noopener noreferrer">events&lt;/a> Percona is going to be in the next months&lt;/p></content:encoded><author>Edith Puclla</author><category>Events</category><category>Opensource</category><category>CNCF</category><category>Kubernetes</category><media:thumbnail url="https://percona.community/blog/2023/05/00-kubeconeu-intro_hu_70f7290cee15e82a.jpg"/><media:content url="https://percona.community/blog/2023/05/00-kubeconeu-intro_hu_1dc5373850d16e87.jpg" medium="image"/></item><item><title>PostgreSQL: Query Optimization With Python and PgBouncer</title><link>https://percona.community/blog/2023/04/25/postgresql-query-optimization-with-python-and-pgbouncer/</link><guid>https://percona.community/blog/2023/04/25/postgresql-query-optimization-with-python-and-pgbouncer/</guid><pubDate>Tue, 25 Apr 2023 00:00:00 UTC</pubDate><description> Database application by Nick Youngson CC BY-SA 3.0 Pix4free</description><content:encoded>&lt;p>
&lt;figure>&lt;img width="1200" height="799" sizes="(max-width: 1200px) 100vw, 1200px" srcset="https://percona.community/blog/2023/04/database-application_hu_e085e419e15bf38e.webp 768w, https://percona.community/blog/2023/04/database-application_hu_e801509da513e4ee.webp 480w, https://percona.community/blog/2023/04/database-application_hu_7fc922718e11d784.webp 1200w" src="https://percona.community/blog/2023/04/database-application_hu_7fc922718e11d784.webp" alt="Database application" loading="lazy" />&lt;figcaption>Database application by Nick Youngson CC BY-SA 3.0 Pix4free&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>A few months ago I wrote a few blog posts on how to generate test data for your database project using Python, which you can find on the Percona blog and the Community blog:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/blog/how-to-generate-test-data-for-mysql-with-python/" target="_blank" rel="noopener noreferrer">How To Generate Test Data for MySQL with Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/blog/how-to-generate-test-data-for-mongodb-with-python/" target="_blank" rel="noopener noreferrer">How To Generate Test Data for MongoDB With Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://percona.community/blog/2023/01/09/how-to-generate-test-data-for-your-database-project-with-python/" target="_blank" rel="noopener noreferrer">How To Generate Test Data for Your Database Project With Python&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>The basic idea is to create a script that uses &lt;a href="https://github.com/joke2k/faker" target="_blank" rel="noopener noreferrer">Faker&lt;/a>, a Python library for generating fake data, and what the script does is&lt;/p>
&lt;ul>
&lt;li>Divide the whole process into every CPU core available by implementing multiprocessing&lt;/li>
&lt;li>The script will generate a total of 60 thousand records, divided by the number of CPU cores minus one&lt;/li>
&lt;li>Each set of records is stored in a Pandas DataFrame, then concatenated into a single DataFrame&lt;/li>
&lt;li>The DataFrame is inserted into the database using Pandas’ &lt;code>to_sql&lt;/code> method, and pymongo’s &lt;code>insert_many&lt;/code> method&lt;/li>
&lt;/ul>
&lt;p>How can the script be optimized? Instead of generating the data, storing it in a DataFrame, and then inserting it into the database, you can make every CPU core insert the data while generating it without storing it elsewhere before running the corresponding SQL statements. Multiprocessing is implemented to use every CPU core available but you also need to configure a connection pool for your PostgreSQL server.&lt;/p>
&lt;p>Through this blog post, you will learn how to install and configure PgBouncer with Python to implement a connection pool for your application.&lt;/p>
&lt;h2 id="pgbouncer">PgBouncer&lt;/h2>
&lt;p>&lt;a href="https://www.pgbouncer.org/" target="_blank" rel="noopener noreferrer">PgBouncer&lt;/a> is a PostgreSQL connection pooler. Any target application can be connected to PgBouncer as if it were a PostgreSQL server, and PgBouncer will create a connection to the actual server, or it will reuse one of its existing connections.&lt;/p>
&lt;p>The aim of PgBouncer is to lower the performance impact of opening new connections to PostgreSQL.&lt;/p>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;p>Ir you’re an Ubuntu user, you can install PgBouncer from the repositories:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo apt install pgbouncer -y&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If not available in the repositories, you can follow the instructions below for both Debian and Ubuntu as mentioned in the Scaleway documentation&lt;/p>
&lt;ol>
&lt;li>Create the &lt;code>apt&lt;/code> repository configuration file&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Import the repository signing key&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Update the &lt;code>apt&lt;/code> package manager&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo apt update&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Install PgBouncer using &lt;code>apt&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo apt install pgbouncer -y&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="configuration">Configuration&lt;/h3>
&lt;p>After installing PgBouncer, edit the configuration files, as stated in the Scaleway &lt;a href="https://www.scaleway.com/en/docs/tutorials/install-pgbouncer/" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;ol>
&lt;li>Set up the PostgreSQL server details in &lt;code>/etc/pgbouncer/pgbouncer.ini&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">database_name = host=localhost port=5432 dbname=database_name&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You may also want to set &lt;code>listen_addr&lt;/code> to &lt;code>*&lt;/code> if you want to to listen to TCP connections on all addresses or set a list of IP addresses.&lt;/p>
&lt;p>Default &lt;code>listen_port&lt;/code> is &lt;code>6432&lt;/code>&lt;/p>
&lt;p>From &lt;a href="https://www.compose.com/articles/how-to-pool-postgresql-connections-with-pgbouncer/" target="_blank" rel="noopener noreferrer">this article&lt;/a> by Abdullah Alger, the settings &lt;code>max_client_conn&lt;/code> and &lt;code>default_pool_size&lt;/code>, the former refers to the number of applications that will make connections and the latter is how many server connections per database. The defaults are set at &lt;code>100&lt;/code> and &lt;code>20&lt;/code>, respectively.&lt;/p>
&lt;ol start="2">
&lt;li>Edit the &lt;code>/etc/pgbouncer/userlist.txt&lt;/code> file and add your PostgreSQL credentials&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">“username” “password”&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Add the IP address of the PgBouncer server to the PostgreSQL &lt;code>pg_hba.conf&lt;/code> file&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">host all all PGBOUNCER_IP/NETMASK trust&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>By default, PgBouncer comes with &lt;code>trust&lt;/code> authentication method. The trust method can be used in a development environment but is not recommended for production. For production, &lt;code>hba&lt;/code> authentication is recommended.&lt;/p>
&lt;ol start="4">
&lt;li>After configuring PgBouncer, restart both the PostgreSQL and PgBouncer services&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sudo systemctl reload postgresql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl reload pgbouncer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For more information about additional configuration options, check the PgBouncer &lt;a href="https://www.pgbouncer.org/config.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;h2 id="python">Python&lt;/h2>
&lt;h3 id="requirements">Requirements&lt;/h3>
&lt;h4 id="dependencies">Dependencies&lt;/h4>
&lt;p>Make sure all the dependencies are installed before creating the Python script that will generate the data for your project.&lt;/p>
&lt;p>You can create a &lt;code>requirements.txt&lt;/code> file with the following content:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tqdm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">faker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psycopg2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or if you’re using Anaconda, create an &lt;code>environment.yml&lt;/code> file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">name: percona
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dependencies:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - python=3.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - tqdm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - faker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - psycopg2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can change the Python version as this script has been proven to work with these versions of Python: 3.7, 3.8, 3.9, 3.10, and 3.11.&lt;/p>
&lt;p>Run the following command if you’re using &lt;code>pip&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pip install -r requirements.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or run the following statement to configure the project environment when using Anaconda:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">conda env create -f environment.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="database">Database&lt;/h4>
&lt;p>Now that you have the dependencies installed, you must create a database named &lt;code>company&lt;/code>.&lt;/p>
&lt;p>Log into PostgreSQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo su postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ psql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create the &lt;code>company&lt;/code> database:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">create database company;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And create the &lt;code>employees&lt;/code> table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">create table employees(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id serial primary key,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fist_name varchar(50) not null,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> last_name varchar(50) not null,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> job varchar(100) not null,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> address varchar(200) not null,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> city varchar(100) not null,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> email varchar(50) not null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="inserting-data">Inserting Data&lt;/h3>
&lt;p>Now it’s time to create the Python script that will generate the data and insert it into the database.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">from multiprocessing import Pool, cpu_count
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">import psycopg2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">from tqdm import tqdm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">from faker import Faker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fake = Faker()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">num_cores = cpu_count() - 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">def insert_data(arg):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> x = int(60000/num_cores)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> print(x)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> with psycopg2.connect(database="database_name", user="user", password="password", host="localhost", port="6432") as conn:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> with conn.cursor() as cursor:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for i in tqdm(range(x), desc="Inserting Data"):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sql = "INSERT INTO employees (first_name, last_name, job, address, city, email) VALUES (%s, %s, %s, %s, %s, %s)"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> val = (fake.first_name(), fake.last_name(), fake.job(), fake.address(), fake.city(), fake.email())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cursor.execute(sql, val)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if __name__=="__main__":
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> with Pool() as pool:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pool.map(insert_data, range(num_cores))&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At first, the multiprocessing pool is created, and configured to use all available CPU cores minus one. Each core will call the &lt;code>insert_data()&lt;/code> function.&lt;/p>
&lt;p>On each call to the function, a connection to the database will be established through the default port (6432) of PgBouncer, meaning that the application will open a number of connections equal to &lt;code>num_cores&lt;/code>, a variable that contains the number of CPU cores being used.&lt;/p>
&lt;p>Then, the data will be generated with Faker and inserted into the database by executing the corresponding SQL statements.&lt;/p>
&lt;p>In a CPU with 16 cores, the number of records inserted into the database on each call to the function will be equal to 60 thousand divided by 15, that is 4 thousand SQL statements executed.&lt;/p>
&lt;p>This way you can modify the script and optimize it by configuring a connection pool with PgBouncer.&lt;/p></content:encoded><author>Mario García</author><category>PostgreSQL</category><category>Python</category><media:thumbnail url="https://percona.community/blog/2023/04/database-application_hu_8c3750ef5226c2f2.jpg"/><media:content url="https://percona.community/blog/2023/04/database-application_hu_4464c2ce00914a53.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.37 preview release</title><link>https://percona.community/blog/2023/04/20/preview-release/</link><guid>https://percona.community/blog/2023/04/20/preview-release/</guid><pubDate>Thu, 20 Apr 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.37 preview release Hello folks! Percona Monitoring and Management (PMM) 2.37 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-237-preview-release">Percona Monitoring and Management 2.37 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.37 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>You can find the Release Notes &lt;a href="https://pmm-2-37-0-pr-1043.onrender.com/release-notes/2.37.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker-installation">Percona Monitoring and Management server docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.37.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variable when starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.37.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.37 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-5256.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.37.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.37.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-013c92f3d0c727b8f&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us in &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">https://forums.percona.com/&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Release</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>FerretDB - A Quick Look</title><link>https://percona.community/blog/2023/04/12/ferretdb-a-quick-look/</link><guid>https://percona.community/blog/2023/04/12/ferretdb-a-quick-look/</guid><pubDate>Wed, 12 Apr 2023 00:00:00 UTC</pubDate><description>There is an old saying that what looks like a duck and quacks like a duck is probably a duck. But what looks like MongoDB and acts like MongoDB could be FerretDB! To greatly simplify the technology behind this project, FerretDB speaks, or quacks, MongoDB but stores the data in PostgreSQL. PostgreSQL has had a rich JSON data environment for years and FerrtDB takes advantage of this capability. This is a truly Open Source MongoDB alternative and was released under the Apache 2.0 license.</description><content:encoded>&lt;p>There is an old saying that what looks like a duck and quacks like a duck is probably a duck. But what looks like MongoDB and acts like MongoDB could be FerretDB! To greatly simplify the technology behind this project, FerretDB speaks, or quacks, MongoDB but stores the data in PostgreSQL. PostgreSQL has had a rich JSON data environment for years and FerrtDB takes advantage of this capability. This is a truly Open Source MongoDB alternative and was released under the Apache 2.0 license.&lt;/p>
&lt;p>FerretDB has been in development for a while, but they &lt;a href="https://blog.ferretdb.io/ferretdb-1-0-ga-opensource-mongodb-alternative/" target="_blank" rel="noopener noreferrer">announced&lt;/a> the first Generally Available Release of their product recently.&lt;/p>
&lt;p>In the announcement is a quick “How To Get Started” section which details how to get FerretDB running with the help of Docker. As can be seen below, this is a very simple process.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker run -d --rm --name ferretdb -p 27017:27017 ghcr.io/ferretdb/all-in-one
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Unable to find image 'ghcr.io/ferretdb/all-in-one:latest' locally
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">latest: Pulling from ferretdb/all-in-one
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">f1f26f570256: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1c04f8741265: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dffc353b86eb: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">18c4a9e6c414: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">81f47e7b3852: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5e26c947960d: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a2c3dc85e8c3: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">17df73636f01: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">713535cdf17c: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">52278a39eea2: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4ded87da67f6: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">05fae4678312: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">56b4f4aeea2d: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">68c486387c4f: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5eb3eee800a9: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">8e5dd809e820: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">d3e85fce5b45: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">e6810cdbd43b: Pull complete
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Digest: sha256:072312577c1daf469ac77d09284a638dea98b63f4f4334fd54959324847b93aa
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Status: Downloaded newer image for ghcr.io/ferretdb/all-in-one:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">58f00a86bad172674479f3663563af274e0dd3d15249029a403d0c85039b7ab5&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now that FerretDB is ready, we can use the MondoDB shell to speak to it.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker exec -it ferretdb mongosh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Current Mongosh Log ID: 6435963392d12db06bdb7ecc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&amp;serverSelectionTimeoutMS=2000&amp;appName=mongosh+1.8.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using MongoDB: 6.0.42
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Using Mongosh: 1.8.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(the remaining output was omitted for brevity)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Entering some very basic MongoDB commands work as expected. Well, for the most part.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">test> db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> show collections;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> db.createCollection('test');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{ ok: 1 }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> show collections;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">test> db.test.insert({name: "Dave", state: "Texas"});
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> acknowledged: true,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insertedIds: { '0': ObjectId("6435ac52c4a22ac27f30e2a2") }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> db.test.insertOne({name: "Dave", state: "Texas"});
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> acknowledged: true,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insertedId: ObjectId("6435ac5dc4a22ac27f30e2a3")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> db.test.find();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: ObjectId("6435ac52c4a22ac27f30e2a2"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: 'Dave',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> state: 'Texas'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: ObjectId("6435ac5dc4a22ac27f30e2a3"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: 'Dave',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> state: 'Texas'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I expected that the inset on the deprecated ’insert’ command would not create a document but I was wrong. It took me a moment to realize that the ‘insert’ and ‘insertOne’ commands both worked after looking at the different ObjectIds.
But what do we know about the server itself? Issuing a serverStatus commands confirms we are talking to the FerretDB server.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">test> db.runCommand({serverStatus: 1});
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host: '58f00a86bad1',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> version: '6.0.42',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> process: 'ferretdb',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid: Long("10"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uptime: 6277.435694035,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uptimeMillis: Long("6277435"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uptimeEstimate: Long("6277"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> localTime: ISODate("2023-04-11T18:59:46.488Z"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> freeMonitoring: { state: 'undecided' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> metrics: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> commands: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ping: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> getFreeMonitoringStatus: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> create: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insert: { total: Long("3"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> atlasVersion: { total: Long("1"), failed: Long("1") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> getLog: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> buildInfo: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> getCmdLineOpts: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listCollections: { total: Long("2"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ismaster: { total: Long("611"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> find: { total: Long("4"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> getParameter: { total: Long("1"), failed: Long("1") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hello: { total: Long("1"), failed: Long("0") },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unknown: { total: Long("5"), failed: Long("0") }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ok: 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> catalogStats: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> collections: 210,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> capped: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> views: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> internalCollections: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> internalViews: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test> &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>FerretDB is a MongoDB protocol server built upon PostgreSQL. Those unhappy with the change in MongoDB’s license change away from open source now have another path they can follow. I will have a full session at &lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live&lt;/a> on &lt;a href="https://www.ferretdb.io/" target="_blank" rel="noopener noreferrer">FerretDB&lt;/a> where I will delve into how complete of an option this is for those desiring an open solution.&lt;/p></content:encoded><author>David Stokes</author><category>FerretDB</category><category>MongoDB</category><category>Databases</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2023/04/Ferret-1200_hu_f84f546d74aa0f3b.jpg"/><media:content url="https://percona.community/blog/2023/04/Ferret-1200_hu_1eab71f1011408b4.jpg" medium="image"/></item><item><title>​​Using the JSON data type with MySQL 8 - Part II</title><link>https://percona.community/blog/2023/04/11/using-the-json-data-type-with-mysql-8-ii/</link><guid>https://percona.community/blog/2023/04/11/using-the-json-data-type-with-mysql-8-ii/</guid><pubDate>Tue, 11 Apr 2023 00:00:00 UTC</pubDate><description>If you read - Using the JSON data type with MySQL 8 - Part I, you will see that inserting data into MySQL of JSON type is a very common and effective practice. Now we’ll see how to do it with a Python project, using SQLAlchemy and Docker Compose, which further automates this example. You can run this example using a single command: docker-compose up</description><content:encoded>&lt;p>If you read - &lt;a href="https://percona.community/blog/2023/03/13/using-the-json-data-type-with-mysql-8/" target="_blank" rel="noopener noreferrer">Using the JSON data type with MySQL 8 - Part I&lt;/a>, you will see that inserting data into &lt;strong>MySQL&lt;/strong> of &lt;strong>JSON&lt;/strong> type is a very common and effective practice. Now we’ll see how to do it with a &lt;strong>Python&lt;/strong> project, using &lt;strong>SQLAlchemy&lt;/strong> and &lt;strong>Docker Compose&lt;/strong>, which further automates this example. You can run this example using a single command: &lt;strong>docker-compose up&lt;/strong>&lt;/p>
&lt;p>Before getting down to work, we will review some important concepts:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Percona Server for MySQL&lt;/strong> is an open source, drop-in replacement for MySQL Community that provides better performance, more scalability, and enhanced security features.&lt;/li>
&lt;li>&lt;strong>SQLAlchemy&lt;/strong> is a library that allows us to communicate between Python programs and databases.&lt;/li>
&lt;li>&lt;strong>Docker Compose&lt;/strong> is a tool for defining and running multi-container Docker applications.&lt;/li>
&lt;/ul>
&lt;p>Let’s start with the structure of this project:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="704" height="550" sizes="(max-width: 704px) 100vw, 704px" srcset="https://percona.community/blog/2023/04/01-mjii-folders_hu_a8ca208a6be987c.webp 480w, https://percona.community/blog/2023/04/01-mjii-folders_hu_36c9f4d2c1ac1395.webp 704w" src="https://percona.community/blog/2023/04/01-mjii-folders_hu_36c9f4d2c1ac1395.webp" alt="Project folder structure" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We have a folder called &lt;strong>app&lt;/strong> which contains the &lt;strong>db.py&lt;/strong> file, and this is where we create the &lt;strong>library&lt;/strong> database and establish the connection with this database.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">db_user&lt;/span> &lt;span class="o">=&lt;/span> os.environ&lt;span class="o">[&lt;/span>&lt;span class="s1">'DB_USER'&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">db_password&lt;/span> &lt;span class="o">=&lt;/span> os.environ&lt;span class="o">[&lt;/span>&lt;span class="s1">'DB_PASSWORD'&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">engine&lt;/span> &lt;span class="o">=&lt;/span> create_engine&lt;span class="o">(&lt;/span>f&lt;span class="s2">"mysql+pymysql://{db_user}:{db_password}@db:3306/library"&lt;/span>&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this file, we also create the class transactions. This will create the fields for the &lt;strong>library&lt;/strong> databases with &lt;strong>SQLAlchemy&lt;/strong>; we define the attributes, and they will be the database fields.
We have four attributes: book_id, tittle, publishes, and labels. The last one (labels) of JSON data type.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">class transactions&lt;span class="o">(&lt;/span>base&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'book'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">book_id&lt;/span> &lt;span class="o">=&lt;/span> Column&lt;span class="o">(&lt;/span>Integer, &lt;span class="nv">primary_key&lt;/span>&lt;span class="o">=&lt;/span>True&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">title&lt;/span> &lt;span class="o">=&lt;/span> Column&lt;span class="o">(&lt;/span>String&lt;span class="o">(&lt;/span>50&lt;span class="o">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">publisher&lt;/span> &lt;span class="o">=&lt;/span> Column&lt;span class="o">(&lt;/span>String&lt;span class="o">(&lt;/span>50&lt;span class="o">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">labels&lt;/span> &lt;span class="o">=&lt;/span> Column&lt;span class="o">(&lt;/span>JSON&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> def __init__&lt;span class="o">(&lt;/span>self, book_id, title, publisher, labels&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> self.book_id &lt;span class="o">=&lt;/span> book_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> self.title &lt;span class="o">=&lt;/span> title
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> self.publisher &lt;span class="o">=&lt;/span> publisher
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> self.labels &lt;span class="o">=&lt;/span> labels
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">base.metadata.create_all&lt;span class="o">(&lt;/span>engine&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now let’s review the Python script called &lt;strong>insert.py&lt;/strong>, where we use the transactions class to insert data into the database.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">import db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">from sqlalchemy.orm import sessionmaker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">Session&lt;/span> &lt;span class="o">=&lt;/span> sessionmaker&lt;span class="o">(&lt;/span>&lt;span class="nv">bind&lt;/span>&lt;span class="o">=&lt;/span>db.engine&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">session&lt;/span> &lt;span class="o">=&lt;/span> Session&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">tr1&lt;/span> &lt;span class="o">=&lt;/span> db.transactions&lt;span class="o">(&lt;/span>1,&lt;span class="s1">'Green House'&lt;/span>, &lt;span class="s1">'Joe Monter'&lt;/span>, &lt;span class="s1">'{"about" : {"gender": "action", "cool": true, "notes": "labeled"}}'&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">session.add&lt;span class="o">(&lt;/span>tr1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">session.commit&lt;span class="o">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now let’s explore the &lt;strong>docker-compose.yaml&lt;/strong> file, we have two services, the db and the api&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">version: &lt;span class="s2">"3.8"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> api:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> build: .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> container_name: api
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> depends_on:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> condition: service_healthy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: percona/percona-server:8.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> container_name: db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MYSQL_USER: root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MYSQL_ROOT_PASSWORD: root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MYSQL_DATABASE: library
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> healthcheck:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> test: &lt;span class="o">[&lt;/span>&lt;span class="s2">"CMD"&lt;/span>, &lt;span class="s2">"mysqladmin"&lt;/span>, &lt;span class="s2">"ping"&lt;/span>, &lt;span class="s2">"-h"&lt;/span>, &lt;span class="s2">"localhost"&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeout: 20s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> retries: &lt;span class="m">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - my-db:/var/lib/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">"3306:3306"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expose:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">"3306"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Names for volume&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> my-db:&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;strong>db&lt;/strong> service uses the &lt;strong>Percona Server for MySQL&lt;/strong> image (percona/percona-server:8.0) for the database and has a healthcheck that allows you to confirm when the database is started and ready to receive requests.
The &lt;strong>api&lt;/strong> service depends on the &lt;strong>db&lt;/strong> service to start. The api service will build a Dockerfile, it does a build of the Python applications (of db.py and insert.py), so in this way, we can insert data into the database when it is ready.&lt;/p>
&lt;p>It’s time to see the example in action; let’s locate it inside the &lt;strong>json-mysql&lt;/strong> project and run &lt;strong>docker-compose ps -d&lt;/strong>&lt;/p>
&lt;p>Once this is done, we can connect to the database and query the table without needing to go inside the container with the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -i db mysql -uroot -proot &lt;span class="o">&lt;&lt;&lt;&lt;/span> &lt;span class="s2">"use library;show tables;select \* from book;describe book;"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can check the data types of our fields and the inserted data. You will also see the JSON data type “labels” data type.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">book_id title publisher labels
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 Green House Joe Monter &lt;span class="s2">"{\\"&lt;/span>about&lt;span class="se">\\&lt;/span>&lt;span class="s2">" : {\\"&lt;/span>gender&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>action&lt;span class="se">\\&lt;/span>&lt;span class="s2">", \\"&lt;/span>cool&lt;span class="se">\\&lt;/span>&lt;span class="s2">": true, \\"&lt;/span>notes&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>labeled&lt;span class="se">\\&lt;/span>&lt;span class="s2">"}}"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2 El camino Daniil Zotl &lt;span class="s2">"{\\"&lt;/span>about&lt;span class="se">\\&lt;/span>&lt;span class="s2">" : {\\"&lt;/span>gender&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>documental&lt;span class="se">\\&lt;/span>&lt;span class="s2">", \\"&lt;/span>cool&lt;span class="se">\\&lt;/span>&lt;span class="s2">": true, \\"&lt;/span>notes&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>labeled&lt;span class="se">\\&lt;/span>&lt;span class="s2">"}}"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3 London Bridge Mario Mesa &lt;span class="s2">"{\\"&lt;/span>about&lt;span class="se">\\&lt;/span>&lt;span class="s2">" : {\\"&lt;/span>gender&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>drama&lt;span class="se">\\&lt;/span>&lt;span class="s2">", \\"&lt;/span>cool&lt;span class="se">\\&lt;/span>&lt;span class="s2">": true, \\"&lt;/span>notes&lt;span class="se">\\&lt;/span>&lt;span class="s2">": \\"&lt;/span>labeled&lt;span class="se">\\&lt;/span>&lt;span class="s2">"}}"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">Field Type Null Key Default Extra
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">book_id int NO PRI NULL auto_increment
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">title varchar&lt;span class="o">(&lt;/span>50&lt;span class="o">)&lt;/span> YES NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">publisher varchar&lt;span class="o">(&lt;/span>50&lt;span class="o">)&lt;/span> YES NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">labels json YES NULL&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Use “docker compose ps” to see your services running. In this case, we have the “db” service running, which is for the database, and we have “api” with the state “exited,” which means that the scripts to create the database and insert the data into the database “library” was created.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">NAME COMMAND SERVICE STATUS PORTS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">api &lt;span class="s2">"/bin/sh -c 'bash -C…"&lt;/span> api exited &lt;span class="o">(&lt;/span>0&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">db &lt;span class="s2">"/docker-entrypoint.…"&lt;/span> db running &lt;span class="o">(&lt;/span>healthy&lt;span class="o">)&lt;/span> 0.0.0.0:3306->3306/tcp, 33060/tcp&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It was an example of inserting JSON data into MySQL using SQLAlchemy in Python and docker-compose for deployment.&lt;/p>
&lt;p>You can find the project on &lt;a href="//github.com/edithturn/json-mysql.git">GitHub&lt;/a>. If there is any other way to make it better happy to hear it so I can improve this project.&lt;/p>
&lt;p>You can explore more about &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a>, and if you want to see how this project start check &lt;a href="https://percona.community/blog/2023/03/13/using-the-json-data-type-with-mysql-8/" target="_blank" rel="noopener noreferrer">Using the JSON data type with MySQL 8 - Part I&lt;/a>&lt;/p></content:encoded><author>Edith Puclla</author><category>JSON</category><category>MySQL</category><category>Databases</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2023/04/00-mjii-intro_hu_cb63b3892f2127a0.jpg"/><media:content url="https://percona.community/blog/2023/04/00-mjii-intro_hu_9e1506643b3b38f0.jpg" medium="image"/></item><item><title>How a Database Monitoring Tool Can Help a Developer. The Story of One Mistake.</title><link>https://percona.community/blog/2023/04/07/how-a-database-monitoring-tool-can-help-a-developer.-the-story-of-one-mistake./</link><guid>https://percona.community/blog/2023/04/07/how-a-database-monitoring-tool-can-help-a-developer.-the-story-of-one-mistake./</guid><pubDate>Fri, 07 Apr 2023 00:00:00 UTC</pubDate><description>I will tell you the real story of using database monitoring tools when developing an application. I will show you an example of how I managed to detect and fix a problem in the application.</description><content:encoded>&lt;p>I will tell you the real story of using database monitoring tools when developing an application. I will show you an example of how I managed to detect and fix a problem in the application.&lt;/p>
&lt;p>&lt;em>A small clarification, the real story from my development practice happened a little more than a week ago, but for the article I took graphs of final debugging, so that the graphs show the correct sequence and fit into the available for explanation and demonstration. It’s just that in reality, I went out for coffee several times and thought for a long time about what was reflected in the graphs of monitoring:)&lt;/em>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1510" height="882" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/start-new-feature_hu_afce7dc3e622e471.webp 768w, https://percona.community/blog/2023/04/start-new-feature_hu_f58402df1cf2d751.webp 480w, https://percona.community/blog/2023/04/start-new-feature_hu_e29bb30412fe5b5a.webp 1400w" src="https://percona.community/blog/2023/04/start-new-feature_hu_e29bb30412fe5b5a.webp" alt="How a Database Monitoring Tool Can Help a Developer" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1096" height="924" sizes="(max-width: 1096px) 100vw, 1096px" srcset="https://percona.community/blog/2023/04/pmm-image-1_hu_6f466cc4bbac5cbb.webp 768w, https://percona.community/blog/2023/04/pmm-image-1_hu_fa7125fc2f766563.webp 480w, https://percona.community/blog/2023/04/pmm-image-1_hu_e4615bf66095b28b.webp 1096w" src="https://percona.community/blog/2023/04/pmm-image-1_hu_e4615bf66095b28b.webp" alt="How a Database Monitoring Tool Can Help a Developer" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="about-the-app-and-the-process">About the app and the process&lt;/h2>
&lt;p>I am developing a PHP application using MongoDB as a database. The application is lightweight, and most load falls on the database. I have implemented functions at the application level to adjust the number of queries, as the application can quickly load the database to 100%.&lt;/p>
&lt;p>For development, I use several small dev instances in AWS, use Percona Server for MongoDB with three nodes as a database, and have Percona Monitoring and Management (PMM) installed for monitoring the databases.&lt;/p>
&lt;p>My development process consists of the following steps:&lt;/p>
&lt;ol>
&lt;li>I developed a new feature and ran it on the dev server for testing.&lt;/li>
&lt;li>I check the prefiling on the PHP side, and there is no memory leak, and I am happy with the speed.&lt;/li>
&lt;li>I check the database monitoring to ensure everything works fine.&lt;/li>
&lt;li>I debug the feature, setting the number and types of queries in the function to balance the number of queries and the load on the database, if necessary.&lt;/li>
&lt;/ol>
&lt;h2 id="adding-new-functionality-to-the-application">Adding new functionality to the application&lt;/h2>
&lt;p>So I started the application and got ready to run the new feature. The feature was getting information from open sources, processing it, and saving it to the database. The second part of the functionality went through all the saved documents and did some additional processing.&lt;/p>
&lt;p>At this point, the application already had a lot of features that loaded the CPU of the Primary Node by 25-40%, and everything was running stably. I decided to have a performance reserve, as I planned to add new features.&lt;/p>
&lt;p>I checked several dashboards, and there were no anomalies or changes. PMM has many dashboards and charts, and I will only show a few, just some.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1022" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/pmm-image-2_hu_3cf4f34648586722.webp 768w, https://percona.community/blog/2023/04/pmm-image-2_hu_2804d6d4900ba2db.webp 480w, https://percona.community/blog/2023/04/pmm-image-2_hu_cc23bad7413de62.webp 1400w" src="https://percona.community/blog/2023/04/pmm-image-2_hu_cc23bad7413de62.webp" alt="Adding new functionality to the application" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I saved the changes with the new feature and pushed it to the dev server to make it work. Then I checked that the function started without errors, and the result was visible in the database. I use MongoDB Compass to check the result of a database entry.&lt;/p>
&lt;h2 id="something-has-gone-differently-than-planned">Something has gone differently than planned.&lt;/h2>
&lt;p>I waited a few minutes and rechecked the dashboard. At first glance, the main screen was fine. However, I was alarmed by the speed of processing. The number of operations has mostly stayed the same.&lt;/p>
&lt;p>I scrolled down through the various charts on the dashboard and saw an anomaly.&lt;/p>
&lt;p>The latency increased, and the app loaded the instance to 100% CPU.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1684" height="1092" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/pmm-image-3_hu_2a7d9a3cc8c247db.webp 768w, https://percona.community/blog/2023/04/pmm-image-3_hu_ce197fd48de03c31.webp 480w, https://percona.community/blog/2023/04/pmm-image-3_hu_f381886c594d75cc.webp 1400w" src="https://percona.community/blog/2023/04/pmm-image-3_hu_f381886c594d75cc.webp" alt="Something has gone differently than planned" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1779" height="760" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/pmm-image-4_hu_118e41086c6adbe9.webp 768w, https://percona.community/blog/2023/04/pmm-image-4_hu_c2fbb5c389d761d8.webp 480w, https://percona.community/blog/2023/04/pmm-image-4_hu_3d15b5ea54f99a66.webp 1400w" src="https://percona.community/blog/2023/04/pmm-image-4_hu_3d15b5ea54f99a66.webp" alt="Something has gone differently than planned" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I have made a test run on the application side and checked the profiler there, too. The app worked poorly, and queries were slow.&lt;/p>
&lt;h2 id="finding-the-cause-of-the-problem">Finding the cause of the problem&lt;/h2>
&lt;p>I knew the reason was the new feature and immediately rolled back the last changes.&lt;/p>
&lt;p>I had a rough idea of where the problem might be, made a few changes, and started again.&lt;/p>
&lt;p>I did it several times, but the result was the same (the CPU was loaded at 100%).&lt;/p>
&lt;p>I selected a period with a load and used the Query Analytics function built into the monitoring.
Query Analytics shows a list of queries sorted by load or execution speed. Some of the queries to the Pages collection gave 90% load, and the Query Time was more than 3 minutes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="901" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/pmm-qan_hu_72a22661590f6dde.webp 768w, https://percona.community/blog/2023/04/pmm-qan_hu_4869e5a733170ab8.webp 480w, https://percona.community/blog/2023/04/pmm-qan_hu_61f54e66f2a7cfd8.webp 1400w" src="https://percona.community/blog/2023/04/pmm-qan_hu_61f54e66f2a7cfd8.webp" alt="Percona Monitoring and Management PMM - MongoDB - QAN" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In Query Analytics, you can find slow queries, see their details, and then debug them in the application.&lt;/p>
&lt;h2 id="fixing-the-problem">Fixing the problem&lt;/h2>
&lt;p>I made a few changes that fixed the problem.&lt;/p>
&lt;p>The first problem was the indexes. I create indexes from within the application using the command.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$app['db']->CollectionName->createIndex(['index_key' => 1]);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Since the application uses many different collections and queries with conditions on various fields and with or without sorting, I have a lot of indexes.&lt;/p>
&lt;p>I made a typo in this case, and the index was not created correctly.&lt;/p>
&lt;p>After the indexes were created correctly, I needed quick runs to debug the number of queries to adjust the CPU load to around 50%.&lt;/p>
&lt;p>You can see the final chart after debugging and fixing the problem.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1322" height="820" sizes="(max-width: 1322px) 100vw, 1322px" srcset="https://percona.community/blog/2023/04/pmm-image-5_hu_7cf252ba5ae90e4d.webp 768w, https://percona.community/blog/2023/04/pmm-image-5_hu_9e9abcb82117c921.webp 480w, https://percona.community/blog/2023/04/pmm-image-5_hu_166edcc7bf87de4.webp 1322w" src="https://percona.community/blog/2023/04/pmm-image-5_hu_166edcc7bf87de4.webp" alt="Percona Monitoring and Management PMM - Fixing the problem" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1588" height="632" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/pmm-image-6_hu_33087e918524f215.webp 768w, https://percona.community/blog/2023/04/pmm-image-6_hu_e6361a772c6a345.webp 480w, https://percona.community/blog/2023/04/pmm-image-6_hu_df8101c5520f43ed.webp 1400w" src="https://percona.community/blog/2023/04/pmm-image-6_hu_df8101c5520f43ed.webp" alt="Percona Monitoring and Management PMM - Fixing the problem" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Don’t forget to add indexes and make sure they work.&lt;/p>
&lt;p>I am a simple developer who can make mistakes and do different experiments. Installing the monitoring was one of the experiments, and previously I just focused on the speed of the PHP script. From time to time, I have looked at the monitoring dashboard in the AWS control panel, but it gives less information, only about the instance itself, without being able to investigate in detail.&lt;/p>
&lt;p>So, &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">PMM&lt;/a> have a great tools for debugging and searching “bottlenecks” in the databases. And I recommend installing and trying database monitoring with PMM if your application uses MySQL, PostgreSQL, or MongoDB.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>PMM</category><category>Monitoring</category><media:thumbnail url="https://percona.community/blog/2023/04/start-new-feature_hu_ed15b8daaede0d34.jpg"/><media:content url="https://percona.community/blog/2023/04/start-new-feature_hu_6276e328e7492af0.jpg" medium="image"/></item><item><title>How To Generate Test Data for Your Database With SQL</title><link>https://percona.community/blog/2023/03/30/how-to-generate-test-data-for-your-database-with-sql/</link><guid>https://percona.community/blog/2023/03/30/how-to-generate-test-data-for-your-database-with-sql/</guid><pubDate>Thu, 30 Mar 2023 00:00:00 UTC</pubDate><description>Recently, I’ve noticed several posts on the Percona Community blog about test data generation. This is a great trend, as such data enables us to test applications more easily and efficiently and detect problems before they appear in production. One article was devoted to the Pagila standard DB schema and another to generating test data with Python. I’ve decided to continue this tradition and write an article about generating data using SQL. For our experimental schema, we’ll use Pagila, but we’ll generate much more data than it currently has.</description><content:encoded>&lt;p>Recently, I’ve noticed several posts on the Percona Community blog about test data generation. This is a great trend, as such data enables us to test applications more easily and efficiently and detect problems before they appear in production. &lt;a href="https://percona.community/blog/2022/12/13/how-to-generate-data-with-pagila-in-percona-distribution-for-postgresql/" target="_blank" rel="noopener noreferrer">One&lt;/a> article was devoted to the Pagila standard DB schema and &lt;a href="https://percona.community/blog/2023/01/09/how-to-generate-test-data-for-your-database-project-with-python/" target="_blank" rel="noopener noreferrer">another&lt;/a> to generating test data with Python. I’ve decided to continue this tradition and write an article about generating data using SQL. For our experimental schema, we’ll use Pagila, but we’ll generate much more data than it currently has.&lt;/p>
&lt;p>And of course, in the current climate, we have no choice but to start with ChatGPT. We will politely ask the bot to generate data for the Pagila (publicly available sample database schema):&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1079" height="1047" sizes="(max-width: 1079px) 100vw, 1079px" srcset="https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_bcd54148b9df7b44.webp 768w, https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_622c55eafc69f9db.webp 480w, https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_d5ed2eca1f426422.webp 1079w" src="https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_d5ed2eca1f426422.webp" alt="ChaGPT respects referential integrity and prudently starts with reference tables" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>As a result, we will obtain several valid SQL scripts in the correct order. However, we may need to request additional details, such as ensuring that all tables are included. On the one hand, this is useful and may already suffice for some cases. On the other hand, there are numerous nuances during data generation, such as specific data distribution and proximity to the subject area. Explaining all of these nuances to the bot may be challenging and labor-intensive. Plus, for sure we will need to generate a large amount of data, and in a very limited time and for private corporate schemas… So let’s roll up our sleeves and go through all the basic steps of generating data from scratch using good old SQL.&lt;/p>
&lt;h2 id="generate-rows">Generate rows&lt;/h2>
&lt;p>As we know, SQL was designed for working with real data stored in tables. However, the SQL:1999 standard introduced &lt;a href="https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL" target="_blank" rel="noopener noreferrer">recursive queries&lt;/a>, which allow, among other things, to generate an arbitrary number of rows without referring to any particular table. At the same time, different DBMSs may have their own (often more convenient) constructs for generating rows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- Standard SQL:1999 way
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">with&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">recursive&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">union&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">all&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">365&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">365&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Oracle
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">level&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dual&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">connect&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">level&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">365&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="generate-values">Generate values&lt;/h2>
&lt;p>So, we already have rows, and now we need data for these rows. To the best of my knowledge, SQL standard does not provide a way to generate random values. However, most DBMSs have their own methods for doing so. With a little tinkering, we can obtain some random data that is remotely similar to real names, emails, dates, and so on. Let’s create 1000 employees for the &lt;code>employee&lt;/code> table without leaving your warm SQL-console:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">employee&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">last_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">years_of_experience&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">order_date&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is_student&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">floor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">99&lt;/span>&lt;span class="p">)::&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'@gmail.com'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">interval&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'90 days'&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">case&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">when&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">then&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">false&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="generate-lifelike-values">Generate lifelike values&lt;/h2>
&lt;p>Thus, we can already generate tons of messy data, which in many cases will be quite enough. But we will not rest on our laurels and will try to generate something closer to real data. Let’s start with one of the most popular task: generating real people’s names. One simple and effective solution is to prepare two sets with common first and last names, respectively, and join them using a Cartesian join. I was impressed by &lt;a href="https://gist.github.com/jbnv/ca5a7829927a6b8f2308" target="_blank" rel="noopener noreferrer">this&lt;/a> GitHub gist:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">last_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">unnest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">array&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">'Adam'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="cm">/*...*/&lt;/span>&lt;span class="s1">'Susan'&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">cross&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">unnest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">array&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">'Matthews'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="cm">/*...*/&lt;/span>&lt;span class="s1">'Hancock'&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">last_name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We were able to generate 1392 unique full names by joining 48 first names and 29 last names. This is a good start, and the same technique can be applied to generate other types of data, such as emails, addresses and so on, by wrapping it in a stored function or using a templating engine like &lt;a href="https://palletsprojects.com/p/jinja/" target="_blank" rel="noopener noreferrer">Jinja&lt;/a> for ease of use. We can also use numerous third-party advanced random data generation services and try to load the generated data into our database (e.g., in CSV format like &lt;a href="https://www.fakenamegenerator.com/" target="_blank" rel="noopener noreferrer">Fake Name Generator&lt;/a>). Some of them will even be able to generate a ready-made SQL script for you (like the &lt;a href="https://generatedata.com/generator" target="_blank" rel="noopener noreferrer">Generatedata&lt;/a> service):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">persons&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">values&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'Berk Cotton'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Tempus Eu Ligula Incorporated'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Ap #633-4301 Tempus, St.'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'interdum.libero.dui@icloud.ca'&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'Ahmed Sandoval'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Nullam Lobortis Foundation'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'P.O. Box 902, 9630 Convallis Rd.'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'magna.suspendisse@google.edu'&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'Hedy Mcbride'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Risus Nulla Limited'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'5235 Lacinia Avenue'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'donec.felis@icloud.com'&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'Kermit Mcintosh'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Erat Associates'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'278-141 Pellentesque St.'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'vel.faucibus@icloud.ca'&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'Susan Berg'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Mauris Institute'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'Ap #876-781 Vehicula Street'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'ipsum.nunc@protonmail.ca'&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The problem of generating data is not new, and there are many popular general-purpose libraries available for generating high-quality fake primitives such as names, addresses, and companies for various programming languages (e.g. &lt;a href="https://github.com/DiUS/java-faker" target="_blank" rel="noopener noreferrer">Java&lt;/a>, &lt;a href="https://github.com/joke2k/faker" target="_blank" rel="noopener noreferrer">Python&lt;/a>, &lt;a href="https://github.com/faker-js/faker" target="_blank" rel="noopener noreferrer">JS&lt;/a>, &lt;a href="https://github.com/faker-ruby/faker" target="_blank" rel="noopener noreferrer">Ruby&lt;/a>, etc.). Fortunately, some databases allow you to work with these libraries and generate more realistic data using SQL queries. For example, &lt;a href="https://gitlab.com/dalibo/postgresql_faker" target="_blank" rel="noopener noreferrer">PostgreSQL Faker&lt;/a> allows you to generate more realistic data using SQL queries like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And that’s not all. Extensions such as &lt;a href="https://github.com/guedes/faker_fdw" target="_blank" rel="noopener noreferrer">faker_fdw&lt;/a> provide a true relational way to generate data, using tables, joins, and other relational features:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ascii_email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">person&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">internet&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="unique-values">Unique values&lt;/h2>
&lt;p>Although our data is random, this does not mean that there are no requirements for it. For example, we may only need unique data for certain columns (e.g. &lt;code>id&lt;/code>, &lt;code>isbn&lt;/code>, &lt;code>code&lt;/code>, etc.). There are several ways to achieve this. For instance, many DBMSs support the &lt;code>upsert&lt;/code> concept based on the values of one or more columns (clauses like &lt;code>merge&lt;/code>, &lt;code>on conflict&lt;/code>, etc.):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">date&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">substr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">interval&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'90 days'&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">conflict&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">do&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">nothing&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can also eliminate duplicate values during generation with the help of the &lt;code>distinct&lt;/code> clause or analytical functions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">partition&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rn&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">substr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">interval&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'90 days'&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">operation_date&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rn&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The disadvantage of the two previous solutions is that we may end up with fewer rows than specified. For greater accuracy, we can use the unique data generation tools built into the DBMS, such as sequences, generators, UUIDs, etc.:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">gen_random_uuid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nextval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'film_film_id_seq'&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- PostgreSQL Faker
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unique_name&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unique_address&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="repeatable-randomness">Repeatable randomness&lt;/h2>
&lt;p>In specific cases, it may be necessary to generate the same random data on every run. This is particularly useful for running tests. To achieve this, many DBMSs and libraries allow you to set the initial value (seed) of the random generator:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- PostgreSQL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setseed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Oracle
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">exec&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dbms_random&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">seed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- PostgreSQL Faker
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">seed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4321&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="avatars">Avatars&lt;/h2>
&lt;p>We may need to generate not only textual data, but also images, such as avatars. There are many special services with APIs for avatar generation, ranging from funny cartoons to real people photos (e.g. &lt;a href="https://www.dicebear.com/" target="_blank" rel="noopener noreferrer">dicebear.com&lt;/a>, &lt;a href="https://api.multiavatar.com/" target="_blank" rel="noopener noreferrer">api.multiavatar.com&lt;/a>, &lt;a href="https://xsgames.co/randomusers/" target="_blank" rel="noopener noreferrer">randomusers&lt;/a>). Let’s try to generate some people with random avatars using the captivating &lt;a href="http://robohash.org/" target="_blank" rel="noopener noreferrer">robohash.org&lt;/a> service:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'https://robohash.org/%s?set=set%s'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">' '&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'_'&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">set_number&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">avatar&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">trunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">set_number&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="300" height="300" sizes="(max-width: 300px) 100vw, 300px" srcset="https://percona.community/blog/2023/04/01-robohash_hu_5d5311c3db847f9e.webp 300w" src="https://percona.community/blog/2023/04/01-robohash_hu_5d5311c3db847f9e.webp" alt="robohash" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="lets-generate-something-real">Let’s generate something real&lt;/h2>
&lt;p>It’s time to do something useful! To conduct our experiments, we require a “guinea pig” database. Let’s use the wonderful and well-known &lt;a href="https://github.com/devrimgunduz/pagila" target="_blank" rel="noopener noreferrer">Pagila&lt;/a> sample database, which we already used with ChatGPT at the very beginning of the post. In fact, Pagila already has data, but it is quite small — a maximum of 16k in just a couple of tables. So we will generate a lot of data ourselves for the empty schema with the help of pure SQL:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2051" height="1478" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_164cf3f975f4771c.webp 768w, https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_ed1fe54694340d71.webp 480w, https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_2caa63047287fe7.webp 1400w" src="https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_2caa63047287fe7.webp" alt="The main part of the Pagila schema for our exercises" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>To make our SQL generation scripts simpler and more readable, and the generated data more realistic, we will use the &lt;a href="https://gitlab.com/dalibo/postgresql_faker" target="_blank" rel="noopener noreferrer">PostgreSQL Faker&lt;/a> Postgres extension in our SQL queries. Fortunately, there is an easy way to obtain this extension by using a Docker image:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p 5432:5432 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --env &lt;span class="nv">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>postgres &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> registry.gitlab.com/dalibo/postgresql_faker&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Also, don’t forget to connect to the database and register the extension:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -it pagila-faker sh -c &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">"psql -U postgres -d postgres \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> -c \"create schema faker;\" \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> -c \"create extension faker schema faker cascade;\" \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> "&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And set up Pagila’s schema, using only the &lt;a href="https://github.com/devrimgunduz/pagila/blob/master/pagila-schema.sql" target="_blank" rel="noopener noreferrer">pagila-schema.sql&lt;/a> script without any data:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/devrimgunduz/pagila.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> pagila
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker cp ./pagila-schema.sql pagila-faker:/docker-entrypoint-initdb.d/pagila-schema.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it pagila-faker sh -c &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">"psql -U postgres -d postgres -f /docker-entrypoint-initdb.d/pagila-schema.sql"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="references">References&lt;/h2>
&lt;p>So let’s start with the easiest stuff — reference tables. In our case, these are things like tables of countries, languages, and so on. In such a situation, we can combine the &lt;code>generate_series&lt;/code> and &lt;code>faker.unique_country&lt;/code> functions (or &lt;code>unique_language_name&lt;/code>, or any other suitable function from the PostgreSQL Faker extension):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unique_country&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A more complex case is the &lt;code>city&lt;/code> reference table because the &lt;code>city&lt;/code> table depends on the &lt;code>country&lt;/code> table. It would be great if each country had a different number of cities, as is usually the case in reality. To achieve this, we can perform a &lt;code>cross join&lt;/code> between the &lt;code>country&lt;/code> table and a sequence of 1000 rows, and then randomly filter out some of the data, 90% in our case:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unique_city&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">cross&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">9&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As a result, we get around 2000 cities with different distributions by country:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>country_id&lt;/th>
&lt;th>country&lt;/th>
&lt;th>cities_per_country&lt;/th>
&lt;th>cities&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>27&lt;/td>
&lt;td>Guinea&lt;/td>
&lt;td>118&lt;/td>
&lt;td>1943&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>32&lt;/td>
&lt;td>Suriname&lt;/td>
&lt;td>108&lt;/td>
&lt;td>1943&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>40&lt;/td>
&lt;td>Marshall Islands&lt;/td>
&lt;td>107&lt;/td>
&lt;td>1943&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>25&lt;/td>
&lt;td>Romania&lt;/td>
&lt;td>103&lt;/td>
&lt;td>1943&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>36&lt;/td>
&lt;td>Micronesia&lt;/td>
&lt;td>103&lt;/td>
&lt;td>1943&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="generate-related-data">Generate related data&lt;/h2>
&lt;p>Okay, now comes the fun part — generating data for the &lt;code>staff&lt;/code> table. This table has two parent tables, &lt;code>store&lt;/code> and &lt;code>address&lt;/code>, and we need to generate their random combinations somehow. The first thing that comes to mind is using a &lt;code>cross join&lt;/code> (as we did already with small reference tables). However, in this case, we may end up with a very slow query, because the Cartesian product of two large tables will generate a huge number of rows that still have to be sorted in random order (and only after that can we cut off the extra rows). Fortunately, the SQL:2003 standard introduces the &lt;code>tablesample&lt;/code> clause. This allows us to read not the entire table, but only a part of it as a percentage. The &lt;code>Bernoulli&lt;/code> sampling method ensures that all blocks of the table are scanned and only some random records are read, which leads to a quite uniform distribution of randomly selected rows. This way, we can subtract only a portion of the random rows from the &lt;code>store&lt;/code> and &lt;code>address&lt;/code> tables, say 1%, and then confidently perform a &lt;code>cross join&lt;/code> between them:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">staff&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">address_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">store_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tablesample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bernoulli&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">cross&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tablesample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bernoulli&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="generate-time-series-data">Generate time-series data&lt;/h2>
&lt;p>We often need to store time-series data in our tables. &lt;a href="https://www.timescale.com/" target="_blank" rel="noopener noreferrer">TimescaleDB&lt;/a> has published three impressive articles (&lt;a href="https://www.timescale.com/blog/how-to-create-lots-of-sample-time-series-data-with-postgresql-generate_series/" target="_blank" rel="noopener noreferrer">one&lt;/a>, &lt;a href="https://www.timescale.com/blog/generating-more-realistic-sample-time-series-data-with-postgresql-generate_series/" target="_blank" rel="noopener noreferrer">two&lt;/a>, &lt;a href="https://www.timescale.com/blog/how-to-shape-sample-data-with-postgresql-generate_series-and-sql/" target="_blank" rel="noopener noreferrer">three&lt;/a>) devoted to generating such data using SQL. However, in this post, we will only focus on generating &lt;code>rental_date&lt;/code> values based on the current row number for the &lt;code>rental&lt;/code> table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rental&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rental_date&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">inventory_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">customer_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">staff_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">current_date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(((&lt;/span>&lt;span class="n">row_number&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">over&lt;/span>&lt;span class="p">())::&lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">' minute'&lt;/span>&lt;span class="p">)::&lt;/span>&lt;span class="nb">interval&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">inventory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tablesample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bernoulli&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">cross&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">customer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tablesample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bernoulli&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">cross&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">staff&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tablesample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bernoulli&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="data-multiplication">Data multiplication&lt;/h2>
&lt;p>In some cases, we need to generate data from scratch, which is what we have done so far. But in other cases, we need to generate new data based on existing data while preserving current distributions and relationships. Let’s try to increase the number of cities in the &lt;code>city&lt;/code> table by four times while maintaining the percentage of cities in each country. The idea is very simple: count the number of cities for each country, multiply it by 3, and generate this number of new cities for each country:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">insert&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">into&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">unnest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unique_city&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cities_count&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cities_count&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">c&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see, there are many more cities, but the top 5 countries have remained the same:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>country_id&lt;/th>
&lt;th>country&lt;/th>
&lt;th>cities_per_country&lt;/th>
&lt;th>cities&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>27&lt;/td>
&lt;td>Guinea&lt;/td>
&lt;td>472&lt;/td>
&lt;td>7772&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>32&lt;/td>
&lt;td>Suriname&lt;/td>
&lt;td>432&lt;/td>
&lt;td>7772&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>40&lt;/td>
&lt;td>Marshall Islands&lt;/td>
&lt;td>428&lt;/td>
&lt;td>7772&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>25&lt;/td>
&lt;td>Romania&lt;/td>
&lt;td>412&lt;/td>
&lt;td>7772&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>36&lt;/td>
&lt;td>Micronesia&lt;/td>
&lt;td>412&lt;/td>
&lt;td>7772&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>This is a viable option for generating data based on existing data, but in some cases, we may need more advanced methods, such as interpolation, prediction/extrapolation, or even machine learning. Perhaps we will discuss this separately in one of the following posts.&lt;/p>
&lt;h2 id="sewing-it-all-together">Sewing it all together&lt;/h2>
&lt;p>The full set of scripts is available in this GitHub repository. Among other things, you can find a &lt;code>docker-compose.yaml&lt;/code> file there, which will allow you to easily set up a test database and run generation scripts for it with just a few commands:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/mgramin/pagila-data-generation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> pagila-data-generation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker-compose up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="theres-still-a-long-way-to-go">There’s still a long way to go…&lt;/h2>
&lt;p>On the one hand, we have described many useful tools and generated a lot of synthetic data for our schema. But on the other hand, we will face many other problems and challenges:&lt;/p>
&lt;ul>
&lt;li>Different database schemas. Our current SQL scripts are rigidly tied to the Pagila database schema. We will have to rewrite the scripts every time for each new supported schema.
Schema Migrations. We will also need to update the scripts every time we migrate the schema.&lt;/li>
&lt;li>Complex Schemas. In the Pagila database, we have about two dozen tables, but in real life, we usually encounter schemas with hundreds and thousands of tables, and complex dependencies between them.&lt;/li>
&lt;li>Data Quality. In this article, we did not imposed strict requirements on the quality and properties of the synthesized data. But in real life, it’s different. We may need a specific distribution of values for certain parameters, localization of values, and much more.&lt;/li>
&lt;li>Specific Data Types. In addition to text, dates, and numeric values (which we have worked with in this post), various DBMSs support many other more complex data types. These include object types, domains, and JSON, among others, which may also need to be processed during generation.&lt;/li>
&lt;li>Performance issues.&lt;/li>
&lt;/ul>
&lt;h2 id="but-not-so-bad">But not so bad&lt;/h2>
&lt;p>Fortunately, there are many ready-made solutions that can solve the aforementioned problems in various ways (an incomplete list can be found and supplemented &lt;a href="https://github.com/mgramin/awesome-db-tools#generators" target="_blank" rel="noopener noreferrer">here&lt;/a>). We will attempt to generate data for our schema using the &lt;a href="https://docs.synthesized.io/tdk/latest/?utm_source=percona&amp;utm_medium=devrel&amp;utm_campaign=datagen" target="_blank" rel="noopener noreferrer">Synthesized TDK&lt;/a> tool. TDK is a YAML-based tool, and for a quick start, we only need to prepare a small configuration file in YAML format and specify the mode and expected number of rows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">default_config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GENERATION&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target_row_number&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100_000&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This configuration is already sufficient to generate test data for both a small Pagila database and for real production schemas with hundreds of tables and relationships. TDK will determine and apply the necessary generation parameters independently depending on the mode, type of columns, and initial data. As a result, TDK will scan all the tables and their data in the input database and generate 100K rows for each table in the output database, taking into account the type of each column and the existing distribution.&lt;/p>
&lt;p>However, we can change the default behavior of TDK by adding custom instructions to the configuration file. Suppose we want movies that are suitable for the whole family (MPAA-rating G — General Audiences) to be the majority. To achieve this, we will add additional configurations to our configuration file to generate the values of the &lt;code>film.rating&lt;/code> column with a specific distribution:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">tables&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">table_name_with_schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"public.film"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target_row_number&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10_000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">transformations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">columns&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"rating"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"categorical_generator"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">categories&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">values&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"G"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"PG"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"PG-13"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"R"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"NC-17"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">probabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0.5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.1&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>By applying this configuration, we can achieve the specified distribution among 10,000 films:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rating&lt;/th>
&lt;th>count&lt;/th>
&lt;th>ratio&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>G&lt;/td>
&lt;td>5006&lt;/td>
&lt;td>50&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PG&lt;/td>
&lt;td>1997&lt;/td>
&lt;td>19&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NC-17&lt;/td>
&lt;td>1048&lt;/td>
&lt;td>10&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PG-13&lt;/td>
&lt;td>976&lt;/td>
&lt;td>9&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>R&lt;/td>
&lt;td>973&lt;/td>
&lt;td>9&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We can also further configure the generation of addresses, names, strings, various sequences, and much more. You can read more about this in the &lt;a href="https://docs.synthesized.io/tdk/latest/user_guide/reference/transformations?utm_source=percona&amp;utm_medium=devrel&amp;utm_campaign=datagen" target="_blank" rel="noopener noreferrer">documentation&lt;/a> on transformation types. TDK also provide easy integration with &lt;a href="https://github.com/marketplace/actions/run-synthesized-tdk" target="_blank" rel="noopener noreferrer">CI/CD pipelines&lt;/a> and &lt;a href="https://synthesized.medium.com/how-synthesized-can-help-populate-your-testcontainers-databases-8168aa7a668" target="_blank" rel="noopener noreferrer">Testcontainers&lt;/a>. And for an even easier start, we have prepared a small demo — &lt;a href="https://github.com/synthesized-io/pagila-tdk-generation.git" target="_blank" rel="noopener noreferrer">pagila-tdk-generation&lt;/a>. It can be run with just a couple of commands using docker-compose:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/synthesized-io/tdk-docker-demo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> tdk-docker-demo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker-compose run tdk&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As a result, two PostgreSQL instances will be created, with port &lt;code>6000&lt;/code> forwarded for the input database and port &lt;code>6001&lt;/code> for the output database. The Pagila schema will be installed on each of them, and the TDK will be launched to generate data using a more advanced &lt;a href="https://github.com/synthesized-io/pagila-tdk-generation/blob/main/config.yaml" target="_blank" rel="noopener noreferrer">configuration&lt;/a>. Once the TDK completes its work, control will return to the command line, and we can connect to the output database to examine the synthesized data (using &lt;code>6001&lt;/code> port and &lt;code>postgres&lt;/code> as the username, password, and database name).&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this post, we have demonstrated how easy it is to generate test data for a database using SQL and its extensions. With these tools, we were able to create a set of SQL scripts that generate test data for the Pagila schema. You can use these scripts as a basis for creating your own scripts for your own databases. However, as your database schema grows and your data quality requirements become more complex, maintaining and developing these scripts can become difficult and expensive. Fortunately, there are many ready-made solutions available, one of which is Synthesized TDK, which we examined in this post.&lt;/p></content:encoded><author>Maksim Gramin</author><category>SQL</category><media:thumbnail url="https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_b013accaf7c1afb8.jpg"/><media:content url="https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data_hu_449fe364af2e22e3.jpg" medium="image"/></item><item><title>How to prevent unauthorized users from connecting to ProxySQL</title><link>https://percona.community/blog/2023/03/30/how-to-prevent-unauthorized-users-from-connecting-to-proxysql/</link><guid>https://percona.community/blog/2023/03/30/how-to-prevent-unauthorized-users-from-connecting-to-proxysql/</guid><pubDate>Thu, 30 Mar 2023 00:00:00 UTC</pubDate><description>ProxySQL is a great load balancer which however suffers from some shortcomings concerning the management of MySQL users. ProxySQL provides a firewall which, in my case, is not complete enough to properly manage users and secure their access. Indeed, this firewall does not accept subnets and keeps unauthorized connections in ProxySQL. We cannot then be sure of not suffering a DDOS attack on our ProxySQL instance. In this article, I will explain how I managed to overcome this problem.</description><content:encoded>&lt;p>ProxySQL is a great load balancer which however suffers from some shortcomings concerning the management of MySQL users. ProxySQL provides a firewall which, in my case, is not complete enough to properly manage users and secure their access. Indeed, this firewall does not accept subnets and keeps unauthorized connections in ProxySQL. We cannot then be sure of not suffering a DDOS attack on our ProxySQL instance. In this article, I will explain how I managed to overcome this problem.&lt;/p>
&lt;h2 id="reminder-of-the-principle-of-connection-through-proxysql">Reminder of the principle of connection through ProxySQL&lt;/h2>
&lt;p>To understand what follows, you have to bear in mind how ProxySQL connects to MySQL. The user connects to Proxysql which then establishes the connection to MySQL. For this, ProxySQL maintains MySQL users in its internal database. The names of MySQL users, their passwords as well as the MySQL destination server are entered in the mysql_users table. At each connection request to a MySQL server, ProxySQL checks the presence of the user in the mysql_users table to connect itself to MySQL with this same user.&lt;/p>
&lt;p>Something is missing, isn’t it?&lt;/p>
&lt;p>Yes, the host associated with each MySQL user is missing!&lt;/p>
&lt;p>In MySQL, users are configured to only be able to connect from ProxySQL. In ProxySQL we don’t have this information. By default, all users can therefore connect to ProxySQL from any IP address and ProxySQL will open connections to MySQL for them. As I specified in the introduction, ProxySQL provides a Firewall to overcome this problem, but this one is not really satisfactory.&lt;/p>
&lt;h2 id="prevent-connection-to-proxysql-with-an-unauthorized-user">Prevent connection to ProxySQL with an unauthorized user&lt;/h2>
&lt;p>In this part, our ProxySQL instance will allow user &lt;em>bob&lt;/em> to connect to the MySQL (&lt;em>mysql_server&lt;/em>) instance. &lt;em>Bob&lt;/em> is allowed to connect from &lt;em>IP_1&lt;/em> but cannot from &lt;em>IP_2&lt;/em>. The ProxySQL instance is running on &lt;em>IP_PROXYSQL&lt;/em>.&lt;/p>
&lt;p>In MySQL, user &lt;em>bob&lt;/em> was created like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'bob'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'IP_PROXYSQL'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IDENTIFIED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'PASSWORD'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In ProxySQL, let’s create &lt;em>bob&lt;/em> like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_users&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">username&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">default_hostgroup&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'bob'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'PASSWORD'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">USERS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">USERS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>and declare the MySQL server like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_servers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hostgroup_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">hostname&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'mysql_server'&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SERVERS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SERVERS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you may have noticed, I didn’t declare the same hostgroup when creating the user and the server. Hostgroup 0 does not correspond to any MySQL server. By default, our user bob will therefore be able to connect to ProxySQL but his queries will not be redirected to any MySQL server. Let’s move on to host management. I will declare each authorized host in the mysql_query_rules table. In ProxySQL, this table is used, among other things, to assign different parameters to a connection. You see what I mean? Let’s declare our rule!&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_query_rules&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">active&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">username&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">client_addr&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">destination_hostgroup&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">apply&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'bob'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'IP_1'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I have just declared a rule indicating that all requests coming from user bob connected from &lt;em>IP_1&lt;/em> must be played on host 1. And icing on the cake, &lt;em>IP_1&lt;/em> can be a subnet (&lt;em>IP_1%&lt;/em>), which would not have could not be possible with the firewall. From now on, bob will be able to perform queries from IP_1 and get results from MySQL. If bob plays a request from IP_2, he will not be able to obtain a result since the hostgroup queried will be 0 which does not correspond to any MySQL server. However, this is not satisfactory. Nothing prevents bob from creating a very large number of connections from &lt;em>IP_2&lt;/em>. It won’t reach any MySQL servers but may be able to crash my ProxySQL instance. It’s time to deal with those unauthorized connections!&lt;/p>
&lt;p>ProxySQL provides a scheduler which will be very useful here. This scheduler will allow us to play a bash script every x ms. I created this script in the ProxySQL datadir:&lt;/p>
&lt;p>&lt;em>kill_connections.sh&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PROXYSQL_USERNAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PROXYSQL_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">2&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PROXYSQL_HOSTNAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"127.0.0.1"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PROXYSQL_PORT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"6032"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql -u&lt;span class="nv">$PROXYSQL_USERNAME&lt;/span> -p&lt;span class="nv">$PROXYSQL_PASSWORD&lt;/span> -h&lt;span class="nv">$PROXYSQL_HOSTNAME&lt;/span> -P&lt;span class="nv">$PROXYSQL_PORT&lt;/span> -e &lt;span class="s2">"SELECT SessionID,user,cli_host FROM stats_mysql_processlist WHERE hostgroup = 0"&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="k">while&lt;/span> &lt;span class="nb">read&lt;/span> SessionID user cli_host&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> &lt;span class="nv">$SessionID&lt;/span> !&lt;span class="o">=&lt;/span> &lt;span class="s2">"SessionID"&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">enabled_account&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>mysql -u&lt;span class="nv">$PROXYSQL_USERNAME&lt;/span> -p&lt;span class="nv">$PROXYSQL_PASSWORD&lt;/span> -h&lt;span class="nv">$PROXYSQL_HOSTNAME&lt;/span> -P&lt;span class="nv">$PROXYSQL_PORT&lt;/span> -se&lt;span class="s2">"SELECT count(*) FROM mysql_query_rules WHERE username = '&lt;/span>&lt;span class="nv">$user&lt;/span>&lt;span class="s2">' and '&lt;/span>&lt;span class="nv">$cli_host&lt;/span>&lt;span class="s2">' LIKE client_addr;"&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">"&lt;/span>&lt;span class="nv">$enabled_account&lt;/span>&lt;span class="s2">"&lt;/span> -eq &lt;span class="m">0&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql -u&lt;span class="nv">$PROXYSQL_USERNAME&lt;/span> -p&lt;span class="nv">$PROXYSQL_PASSWORD&lt;/span> -h&lt;span class="nv">$PROXYSQL_HOSTNAME&lt;/span> -P&lt;span class="nv">$PROXYSQL_PORT&lt;/span> -e &lt;span class="s2">"KILL CONNECTION &lt;/span>&lt;span class="nv">$SessionID&lt;/span>&lt;span class="s2">"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">done&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This script lists all the connections opened in ProxySQL on hostgroup 0. It then checks whether the connected user/host pair is authorized using the mysql_query_rules table. If not, the connection is killed. Let’s activate the scheduler in ProxySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">scheduler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">arg1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">arg2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">interval_ms&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'kill_connections.sh'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'proxysql_admin_user'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'proxysql_admin_password'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SCHEDULER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SCHEDULER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, any connection opened in ProxySQL but not authorized will be automatically killed!&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&lt;em>WARNING:&lt;/em>&lt;/strong> unfortunately, the ProxySQL scheduler does not work like the MySQL scheduler. It is necessary to open the connection from a .sh file and therefore to indicate the ProxySQL administration credentials. These identifiers will then be visible by monitoring the list of server processes. To avoid this problem, I advise you to indicate the identifiers directly in the .sh file and to protect this file correctly on your server.&lt;/p>&lt;/blockquote>
&lt;h2 id="additional-information">Additional Information&lt;/h2>
&lt;p>When I deploy ProxySQL, I always create a rule with a very high rule_id to block unauthorized connections; this is an additional barrier in case I forget something:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_query_rules&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">active&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">destination_hostgroup&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">999999999&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'ProxySQL : Access denied'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This rule redirects unauthorized connections to hostgroup 0 (if ever a user was declared in mysql_users with a hostgroup leading to a MySQL server) and displays an error message for each request.
I create all my rules to manage hosts with a rule_id > or = 10000. This allows me to have 9999 empty slots if I ever want to create other priority rules in mysql_query_rules.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_query_rules&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">active&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">username&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">client_addr&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">apply&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IFNULL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">MAX&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_query_rules&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">MAX&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mysql_query_rules&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rule_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">9999&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'USERNAME'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'HOST'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LOAD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RUNTIME&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">SAVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">QUERY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RULES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DISK&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Don’t hesitate to ask me questions, I’ll be happy to answer them.&lt;/p></content:encoded><author>Valentin TRAËN</author><category>Databases</category><category>MySQL</category><category>ProxySQL</category><category>LoadBalancer</category><media:thumbnail url="https://percona.community/blog/2023/03/proxysql_user_management_cover_hu_56dc53c3bd9592d8.jpg"/><media:content url="https://percona.community/blog/2023/03/proxysql_user_management_cover_hu_4a261bdecc8d22a5.jpg" medium="image"/></item><item><title>How To Optimize the Structure of a Simple PHP Application as Your Project Grows</title><link>https://percona.community/blog/2023/03/28/how-to-optimize-the-structure-of-a-simple-php-application-as-your-project-grows/</link><guid>https://percona.community/blog/2023/03/28/how-to-optimize-the-structure-of-a-simple-php-application-as-your-project-grows/</guid><pubDate>Tue, 28 Mar 2023 00:00:00 UTC</pubDate><description>Let’s discuss the structure of our simple PHP application, folders, files, and functions and transform the project to grow it.</description><content:encoded>&lt;p>Let’s discuss the structure of our simple PHP application, folders, files, and functions and transform the project to grow it.&lt;/p>
&lt;p>You and I have developed a small PHP application that makes an HTTP request to the GitHub API and stores the result in the database.&lt;/p>
&lt;ul>
&lt;li>Step 1 - &lt;a href="https://dev.to/dbazhenov/how-to-develop-a-simple-web-application-using-docker-compose-nginx-php-8-and-mongodb-6-nhi" target="_blank" rel="noopener noreferrer">How to Develop a Simple Web Application Using Docker-compose, Nginx, PHP 8, and MongoDB 6&lt;/a>.&lt;/li>
&lt;li>Step 2 - &lt;a href="https://dev.to/dbazhenov/how-to-make-http-requests-to-api-in-php-app-using-github-api-example-and-write-to-percona-server-for-mongodb-3gi3" target="_blank" rel="noopener noreferrer">How to Make HTTP Requests to API in PHP App Using GitHub API Example and Write to Percona Server for MongoDB&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Our code already performs different functions:&lt;/p>
&lt;ol>
&lt;li>Reading environment variables.&lt;/li>
&lt;li>Connecting to the database.&lt;/li>
&lt;li>Running API queries.&lt;/li>
&lt;li>Looping and writing to the database.&lt;/li>
&lt;/ol>
&lt;p>The code is already hard to fit on the screen, and it’s time to think about dividing the code into files, folders, and functions.&lt;/p>
&lt;p>Frameworks are usually responsible for separating code into folders and files. But we don’t use frameworks, so we’ll do it ourselves. At this stage, we will not make the structure too complicated. Our task is to learn how to change it so that we can change it at any time in the future.&lt;/p>
&lt;p>I prefer to change the structure of files and folders as the project grows, grouping files according to meaning and logic. In this article, we’ll make the first change, and we’ll do them more times in the future.&lt;/p>
&lt;p>Let’s start optimizing our application.&lt;/p>
&lt;h2 id="initializing-and-configuring-the-application">Initializing and configuring the application&lt;/h2>
&lt;p>Usually, PHP scripts are executed sequentially starting with index.php, as in our case.&lt;/p>
&lt;p>At the beginning of index.php, we connect the composer libraries, read environment variables, create a database connection, and create an object for HTTP requests. This will be required for each script. I propose to put this in a separate init.php file.&lt;/p>
&lt;p>Create an app/init.php file and move the initialization to it. We simply move the code from the index.php file, leaving only the logic of the script.&lt;/p>
&lt;p>&lt;em>app/init.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Enabling Composer Packages
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/vendor/autoload.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Get environment variables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$local_conf = getenv();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_USERNAME', $local_conf['DB_USERNAME']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_PASSWORD', $local_conf['DB_PASSWORD']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_HOST', $local_conf['DB_HOST']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Connect to MongoDB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$db_client = new \MongoDB\Client('mongodb://'. DB_USERNAME .':' . DB_PASSWORD . '@'. DB_HOST . ':27017/');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$app['db'] = $db_client->selectDatabase('tutorial');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$app['http'] = new \GuzzleHttp\Client();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You may also notice that I created an array or &lt;code>$app&lt;/code> object, and added HTTP and db as array elements. That way I can use &lt;code>$app&lt;/code> anywhere to pass to functions and have HTTP queries and database handling there.&lt;/p>
&lt;p>And in the index.php file itself, we simply include our init.php&lt;/p>
&lt;p>_app/index.php&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/init.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dd($app);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And print $db and $http with dd() to make sure they work and are available in index.php.&lt;/p>
&lt;p>Leave the rest of the code in index.php unchanged for now and run &lt;code>localhost&lt;/code> in the browser.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1106" height="668" sizes="(max-width: 1106px) 100vw, 1106px" srcset="https://percona.community/blog/2023/03/php-3-structure-1_hu_a878af52186673cd.webp 768w, https://percona.community/blog/2023/03/php-3-structure-1_hu_33932f85875788cc.webp 480w, https://percona.community/blog/2023/03/php-3-structure-1_hu_5e7a8843526214f.webp 1106w" src="https://percona.community/blog/2023/03/php-3-structure-1_hu_5e7a8843526214f.webp" alt="PHP Structure - App Init" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>So we split index.php into two files: init.php and index.php&lt;/p>
&lt;p>When we start localhost, our nginx web server launches index.php, init.php connects there, does initialization and environment variables reading, and then the rest of the index.php code continues.&lt;/p>
&lt;h2 id="lets-get-to-the-functions">Let’s get to the functions&lt;/h2>
&lt;p>Functions are needed to group code that is executed multiple times.
Functions can be initialized either in the executable PHP file itself, next to the code, or in a separate file that includes, such as composer libraries.&lt;/p>
&lt;p>For example, we make a GET HTTP request to the GitHub API at a certain URL. We will probably need to make another type of request to another URL, but it will still be an HTTP request using guzzle.&lt;/p>
&lt;p>I assume in advance that I will have many different functions: general, for GitHub, for the database. So let’s create a func folder in the app folder.&lt;/p>
&lt;p>And in the &lt;code>app/func&lt;/code> folder, create a github.php file. Create our first function there that will request the GitHub API.&lt;/p>
&lt;p>&lt;em>app/func/github.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function fn_github_get_repositories($app)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $http = $app['http'];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $url = 'https://api.github.com/search/repositories';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $params = [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'q' => 'topic:mongodb',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'sort' => 'help-wanted-issues'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> try {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $response = $http->request('GET', $url , [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'query' => $params
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $result = $response->getBody();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $result = json_decode($result, true);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } catch (GuzzleHttp\Exception\ClientException $e) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $response = $e->getResponse();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $responseBodyAsString = $response->getBody()->getContents();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> echo $responseBodyAsString;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return $result;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A little clarification, we will modify this file in the future, the first function is not the best. You should understand that you will need to change frequently, changing parameters and variables within functions.&lt;/p>
&lt;p>Now go back to the index.php, and connect our first function file and run the function.&lt;/p>
&lt;p>&lt;em>app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/init.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/func/github.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$repositories = fn_github_get_repositories($app);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dd($repositories);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run &lt;code>localhost&lt;/code> and see the same result. Our request was executed, and we got the list of repositories.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="786" height="552" sizes="(max-width: 786px) 100vw, 786px" srcset="https://percona.community/blog/2023/03/php-3-structure-2_hu_697c4e1e8a9052d4.webp 768w, https://percona.community/blog/2023/03/php-3-structure-2_hu_87a156c8d5c2803e.webp 480w, https://percona.community/blog/2023/03/php-3-structure-2_hu_4f0c3c6699b58b54.webp 786w" src="https://percona.community/blog/2023/03/php-3-structure-2_hu_4f0c3c6699b58b54.webp" alt="PHP Structure - Functions" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>So we cut our index.php file by more than half. All we have left in there is the database query. At the moment it doesn’t look too complicated to put it in a separate function, but. We need to change it because the $db object, we now have $app[‘db’].&lt;/p>
&lt;p>In addition, the response from the API we have in the variable $repositories, which means that all loops and all operations must be done with it.&lt;/p>
&lt;p>So far, my &lt;code>index.php&lt;/code> file is like this.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Enabling Composer Packages
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/init.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/func/github.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$repositories = fn_github_get_repositories($app);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$app['db']->repositories->createIndex(['id' => 1]);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if (!empty($repositories['items'])) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> foreach($repositories['items'] as $key => $repository) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $updateResult = $db->repositories->updateOne(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'id' => $repository['id'] // query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['$set' => $repository],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['upsert' => true]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dd($repositories['items']);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>app/&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── github.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── vendor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── composer.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── index.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── init.php&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The result of running &lt;code>localhost&lt;/code> in the browser will be the same, we will print the repositories that will be stored in the database.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1676" height="1002" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/php-3-structure-3_hu_ba6720d5d85385a0.webp 768w, https://percona.community/blog/2023/03/php-3-structure-3_hu_caa721754ad256c3.webp 480w, https://percona.community/blog/2023/03/php-3-structure-3_hu_7a7b87e52cee4337.webp 1400w" src="https://percona.community/blog/2023/03/php-3-structure-3_hu_7a7b87e52cee4337.webp" alt="PHP Structure - Result" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I think we should stop for now and continue in the next article.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>As a result of this modification, we learned how to split one script into several scripts and create functions.&lt;/p>
&lt;p>You can divide it into files and folders and functions as you like, as it is convenient to you now or will be convenient in the future. It’s okay to change the structure as your project grows.&lt;/p>
&lt;p>You can check and run the source code in &lt;a href="https://github.com/dbazhenov/nginx-php-mongodb-docker-compose/tree/tutorial_2_structure" target="_blank" rel="noopener noreferrer">the repository&lt;/a>.&lt;/p>
&lt;p>In the next post, we will continue to modify and improve our application. We will add new functions and features. I’m sure we’ll end up with a very useful app, and you’ll learn how to develop it.&lt;/p>
&lt;p>Ask me questions, I’ll be happy to answer them.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>Databases</category><category>Dev</category><category>PHP</category><category>Docker</category><media:thumbnail url="https://percona.community/blog/2023/03/php-3-structure-cover_hu_550accaeca72ee73.jpg"/><media:content url="https://percona.community/blog/2023/03/php-3-structure-cover_hu_22ecc47ed5c1ce4f.jpg" medium="image"/></item><item><title>How to Make HTTP Requests to API in PHP App Using GitHub API Example and Write to Percona Server for MongoDB</title><link>https://percona.community/blog/2023/03/20/how-to-make-http-requests-to-api-in-php-app-using-github-api-example-and-write-to-percona-server-for-mongodb/</link><guid>https://percona.community/blog/2023/03/20/how-to-make-http-requests-to-api-in-php-app-using-github-api-example-and-write-to-percona-server-for-mongodb/</guid><pubDate>Mon, 20 Mar 2023 00:00:00 UTC</pubDate><description>We learn how to work with HTTP requests in PHP and make API requests using the GitHub API as an example. We’ll get the data from the API and save it to the database.</description><content:encoded>&lt;p>We learn how to work with HTTP requests in PHP and make API requests using the GitHub API as an example. We’ll get the data from the API and save it to the database.&lt;/p>
&lt;p>In &lt;a href="https://percona.community/blog/2023/03/17/how-to-develop-a-simple-web-application-using-docker-nginx-php-and-mongodb/" target="_blank" rel="noopener noreferrer">the previous article&lt;/a>, we developed a simple application that connects to the MongoDB database (&lt;a href="https://www.percona.com/software/mongodb/percona-server-for-mongodb?utm_source=percona-community&amp;utm_medium=blog&amp;utm_campaign=daniil" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a>) and writes documents in a loop. We only used &lt;a href="https://getcomposer.org/" target="_blank" rel="noopener noreferrer">Composer&lt;/a> packages to work with MongoDB. We have set up the Docker-compose environment and have the app/ directory where the application code is located. We can edit the code and check the result in the browser without restarting the Docker containers.&lt;/p>
&lt;p>We plan to make HTTP requests to GitHub API. I prefer using the popular PHP HTTP client library &lt;a href="https://packagist.org/packages/guzzlehttp/guzzle" target="_blank" rel="noopener noreferrer">guzzlehttp/guzzle&lt;/a>.&lt;/p>
&lt;p>Here we go!&lt;/p>
&lt;h2 id="1-preparation">1. Preparation&lt;/h2>
&lt;p>Open the project folder in the console or git clone the repository, and open the folder.&lt;/p>
&lt;p>Start Docker-compose&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This will start the containers, and you can run &lt;code>localhost&lt;/code> in the browser.&lt;/p>
&lt;h2 id="2-connect-php-libraries-using-composer">2. Connect PHP libraries using Composer&lt;/h2>
&lt;p>&lt;a href="https://getcomposer.org/" target="_blank" rel="noopener noreferrer">Composer&lt;/a> is a package manager for PHP. You can find out-of-the-box libraries and functions for almost any task. I prefer to use something other than Frameworks for small projects, but just plug-in packages.&lt;/p>
&lt;p>Open the &lt;code>app/composer.json&lt;/code> file in the code editor.&lt;/p>
&lt;p>Add two new packages, guzzle, and dd.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "require": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "guzzlehttp/guzzle": "^7.0", // Guzzle for HTTP
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "larapack/dd": "1.*", // dd for debug
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "mongodb/mongodb": "^1.6",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "ext-mongodb": "^1.6"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Connect to the container with php-fpm&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker exec -it [php-fpm-container-name] bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Update the packages with the command.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">composer update&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That’s it, we have the packages installed, more about Composer is &lt;a href="https://getcomposer.org/doc/01-basic-usage.md" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h2 id="3-lets-choose-an-api-and-read-the-rules-of-use">3. Let’s choose an API and read the rules of use.&lt;/h2>
&lt;p>I chose &lt;a href="https://docs.github.com/en/rest/search?apiVersion=2022-11-28#search-repositories" target="_blank" rel="noopener noreferrer">the GitHub API&lt;/a> as an example because it is available with and without authorization and has good documentation.&lt;/p>
&lt;p>First, we will get a list of repositories. We will use the repository search API for all repositories containing the topic mongodb.&lt;/p>
&lt;p>I opened &lt;a href="https://docs.github.com/en/rest/search?apiVersion=2022-11-28#search-repositories" target="_blank" rel="noopener noreferrer">the REST API&lt;/a> documentation and found the method, parameters, and examples I needed.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2926" height="1668" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-API-1_hu_bc4f97d813af8b6a.webp 768w, https://percona.community/blog/2023/03/PHP-API-1_hu_279147022c142f76.webp 480w, https://percona.community/blog/2023/03/PHP-API-1_hu_7fe0373f0f54aa4a.webp 1400w" src="https://percona.community/blog/2023/03/PHP-API-1_hu_7fe0373f0f54aa4a.webp" alt="the GitHub REST API" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="4-lets-make-the-first-request-to-the-api">4. Let’s make the first request to the API.&lt;/h2>
&lt;p>Open the index.php file in the app folder and add the following code somewhere at the beginning, after the inclusion of &lt;code>vendor/autoload.php&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$http = new \GuzzleHttp\Client();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$url = 'https://api.github.com/search/repositories';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$params = [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'q' => 'topic:mongodb'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">try {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $response = $http->request('GET', $url , [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'query' => $params
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $result = $response->getBody();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $result = json_decode($result, true);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} catch (GuzzleHttp\Exception\ClientException $e) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $response = $e->getResponse();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $responseBodyAsString = $response->getBody()->getContents();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> echo $responseBodyAsString;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dd($result);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To summarize, here is what we did:&lt;/p>
&lt;ol>
&lt;li>Initiated the HTTP client Guzzle.&lt;/li>
&lt;li>Created a variable with a URL from GitHub API.&lt;/li>
&lt;li>Created array with query parameters, in our case q search string.&lt;/li>
&lt;li>Executed request in Try-Catch construct; if a request has errors, an exception will be thrown, and we can read the error.&lt;/li>
&lt;li>Received response results from API into variable $result.&lt;/li>
&lt;li>Converted the response from JSON to an array.&lt;/li>
&lt;li>Printed out the array in a convenient form using dd($result).&lt;/li>
&lt;/ol>
&lt;p>Run &lt;code>localhost&lt;/code> in the browser and see the result.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1410" height="1240" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-API-2_hu_d5fb4face59a8936.webp 768w, https://percona.community/blog/2023/03/PHP-API-2_hu_d07a224986b29952.webp 480w, https://percona.community/blog/2023/03/PHP-API-2_hu_6e693ec46cbab88b.webp 1400w" src="https://percona.community/blog/2023/03/PHP-API-2_hu_6e693ec46cbab88b.webp" alt="the GitHub REST API - first request to the API" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="5-exploring-the-result">5. Exploring the result&lt;/h2>
&lt;p>&lt;code>dd()&lt;/code> is a function that helps print any variable, array, or object. Just list them separated by commas.&lt;/p>
&lt;p>You have printed the response from the API and see the array:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>total_count - says that there are 72945 repositories for our query. This looks like an excellent dataset for our experiments with analytics and the database&lt;/p>
&lt;/li>
&lt;li>
&lt;p>items - contains an array of 30 other arrays with repository data.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>I suggest modifying our API query to get a slightly different result. We’ll add sorting to the query.&lt;/p>
&lt;p>Change the $params array in the index.php file.&lt;/p>
&lt;p>Let’s add sorting by the count of &lt;code>help-wanted-issues&lt;/code>.&lt;/p>
&lt;p>&lt;em>app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$params = [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'q' => 'topic:mongodb+language:php',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'sort' => 'help-wanted-issues'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">];&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run localhost in your browser, and you will see a different set of items in the API response.&lt;/p>
&lt;h2 id="6-lets-save-the-data-to-the-database">6. Let’s save the data to the database&lt;/h2>
&lt;p>Instead of dd(), we add a foreach loop over items and print one repository data.&lt;/p>
&lt;p>&lt;em>app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">if (!empty($result['items'])) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> foreach($result['items'] as $key => $repository) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dd($repository);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Great, we see the data. This will be our document in the collection of repositories.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1886" height="1048" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-API-3_hu_de71f3f9db0e2a3e.webp 768w, https://percona.community/blog/2023/03/PHP-API-3_hu_85b9e379a8267ef5.webp 480w, https://percona.community/blog/2023/03/PHP-API-3_hu_40f034adb1eb3832.webp 1400w" src="https://percona.community/blog/2023/03/PHP-API-3_hu_40f034adb1eb3832.webp" alt="the GitHub REST API - save the data to the database" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Replace &lt;code>dd($repository)&lt;/code> with an insert query to the MongoDB database.&lt;/p>
&lt;p>Remember to move the client db initialization, database connection, and read environment variables.&lt;/p>
&lt;p>&lt;em>app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">if (!empty($result['items'])) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> foreach($result['items'] as $key => $repository) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $updateResult = $db->repositories->updateOne(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'id' => $repository['id'] // query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['$set' => $repository],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['upsert' => true]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dd($result);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run &lt;code>localhost&lt;/code> in your browser. I usually always type dd() at the end, since it stops the script and displays something like dd(‘finish’).&lt;/p>
&lt;p>You may notice that making a updateOne request to the repositories collection, with the parameter upsert. This means that if such a document with an id (‘id’ => $repository[‘id’]) already exists in the database, it will be updated. If not, it will be inserted. This allows me to avoid duplicate data with multiple test queries.&lt;/p>
&lt;h2 id="7-connecting-to-a-database-using-mongodb-compass">7. Connecting to a database using MongoDB Compass.&lt;/h2>
&lt;p>We must see that we did everything correctly and the data appeared in the database.&lt;/p>
&lt;p>Besides, we can play with them.&lt;/p>
&lt;p>Open our new collection and check it out. We can also create an index by the id key in the Indexes tab. If you did not make the index with a PHP query.&lt;/p>
&lt;p>Download and install MongoDB Compass &lt;a href="https://www.mongodb.com/products/compass" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2806" height="1352" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-API-4_hu_e3d66f411eb3ff1f.webp 768w, https://percona.community/blog/2023/03/PHP-API-4_hu_c77f6d427b989b5c.webp 480w, https://percona.community/blog/2023/03/PHP-API-4_hu_c47e031064b8f702.webp 1400w" src="https://percona.community/blog/2023/03/PHP-API-4_hu_c47e031064b8f702.webp" alt="the GitHub REST API - MongoDB Compass" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1676" height="1114" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-API-5_hu_fea435a11e683247.webp 768w, https://percona.community/blog/2023/03/PHP-API-5_hu_6b943b0fb47152f0.webp 480w, https://percona.community/blog/2023/03/PHP-API-5_hu_a967a3586b3c4ae4.webp 1400w" src="https://percona.community/blog/2023/03/PHP-API-5_hu_a967a3586b3c4ae4.webp" alt="the GitHub REST API - MongoDB Compass" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The code of the application at the current stage can be obtained from &lt;a href="https://github.com/dbazhenov/nginx-php-mongodb-docker-compose/tree/tutorial_step_1_api" target="_blank" rel="noopener noreferrer">the repository&lt;/a>.&lt;/p>
&lt;p>We did a great job and figured out how to make requests to the API, as you can make any HTTP requests.&lt;/p>
&lt;p>In my next posts, we’ll figure out how to add authorization and work with API limits. The point is that GitHub lets unauthorized users make a few requests. We aim to get a lot of valuable data from the API and will do that soon.&lt;/p>
&lt;p>We will also devote some time to the structure of the files and folders of our application. It’s already getting big, and we must make it more convenient.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>MongoDB</category><category>Databases</category><category>PHP</category><category>Docker</category><media:thumbnail url="https://percona.community/blog/2023/03/PHP-API-2-cover_hu_2753b0fa409e7d16.jpg"/><media:content url="https://percona.community/blog/2023/03/PHP-API-2-cover_hu_373001bc324d4cc4.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.36 preview release</title><link>https://percona.community/blog/2023/03/20/preview-release/</link><guid>https://percona.community/blog/2023/03/20/preview-release/</guid><pubDate>Mon, 20 Mar 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.36 preview release Hello folks! Percona Monitoring and Management (PMM) 2.36 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-236-preview-release">Percona Monitoring and Management 2.36 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.36 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>You can find the Release Notes &lt;a href="https://two-36-0-pr-1011.onrender.com/release-notes/2.36.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker-installation">Percona Monitoring and Management server docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.36.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.36.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.36 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-5090.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.36.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.36.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-0ce04c507ec1187b1&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us in &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">https://forums.percona.com/&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Release</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>How to Develop a Simple Web Application Using Docker, Nginx, PHP, and Percona Server for MongoDB</title><link>https://percona.community/blog/2023/03/17/how-to-develop-a-simple-web-application-using-docker-nginx-php-and-percona-server-for-mongodb/</link><guid>https://percona.community/blog/2023/03/17/how-to-develop-a-simple-web-application-using-docker-nginx-php-and-percona-server-for-mongodb/</guid><pubDate>Fri, 17 Mar 2023 00:00:00 UTC</pubDate><description>I’m developing an application that takes data from different sources, processes it, and prepares reports. In this series of articles, I will explain how to install and configure the tools, application, and database to develop and run the application.</description><content:encoded>&lt;p>I’m developing an application that takes data from different sources, processes it, and prepares reports. In this series of articles, I will explain how to install and configure the tools, application, and database to develop and run the application.&lt;/p>
&lt;h2 id="about-the-application-and-choice-of-tools">About the application and choice of tools&lt;/h2>
&lt;p>The application I develop gets data from GitHub, Jira, and websites via API, processes it and creates reports according to the desired requirements.&lt;/p>
&lt;p>The application is developed with PHP version 8+ and Nginx as a web server, and &lt;a href="https://www.percona.com/software/mongodb/percona-server-for-mongodb?utm_source=percona-community&amp;utm_medium=blog&amp;utm_campaign=daniil" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> as a database. For local development, I use Docker and Docker-compose.&lt;/p>
&lt;p>I use PHP and Nginx because I’m familiar with them, and it’s a popular stack with lots of documentation and examples. Docker was chosen for the same reason. I used to install Nginx/Apache + PHP + Database in the same container, but over time I found Docker-compose and separate containers more convenient, so now I use docker-compose.&lt;/p>
&lt;p>My application includes the following:&lt;/p>
&lt;ol>
&lt;li>Web application to run in a browser and display reports.&lt;/li>
&lt;li>Сonsole scripts in PHP for bulk data updates in the background on the server.&lt;/li>
&lt;/ol>
&lt;p>As a database for this application, I use MongoDB. There are objective reasons for that:&lt;/p>
&lt;ol>
&lt;li>The API I’m querying for data from gives it back to me page by page in JSON format. I need to do a lot of queries, so my script gets all data and stores it in MongoDB beforehand to create reports without needing to go to API.&lt;/li>
&lt;li>MongoDB is suitable for storing data in JSON and queries with different conditions.&lt;/li>
&lt;li>The data schema from the API can be very different and flexible depending on the service and query. MongoDB allows me to save responses from the API to the database as it is, without complicated processing or preconfiguring the database schema. I didn’t want to spend much time setting up the database table schema.&lt;/li>
&lt;li>Installing and configuring MongoDB for development is easy and does not require great skills to work with it.&lt;/li>
&lt;/ol>
&lt;p>I use Percona Server for MongoDB because it’s free and open source. I once thought about backups and monitoring, and Percona has ready-made solutions for that.&lt;/p>
&lt;p>First, I will talk about my development configuration. I am starting from scratch using a minimal PHP application as an example.&lt;/p>
&lt;h2 id="preparing-docker-and-docker-compose">Preparing Docker and Docker-compose&lt;/h2>
&lt;h3 id="dockerfile-for-phm--mongodb">Dockerfile for PHM + MongoDB&lt;/h3>
&lt;p>For PHP to work with MongoDB, we need to install PHP with the required extensions.
I prepared a Dockerfile for PHP 8.2 and used php-fpm because I use Nginx as a web server.&lt;/p>
&lt;p>&lt;em>Dockerfile&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">FROM php:8.2-fpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RUN apt-get -y update \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;&amp; apt-get install -y libssl-dev pkg-config libzip-dev unzip git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RUN pecl install zlib zip mongodb \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;&amp; docker-php-ext-enable zip \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;&amp; docker-php-ext-enable mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Install composer (updated via entry point)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I also used Composer and installed it immediately in the container with Dockerfile.&lt;/p>
&lt;p>Now I run the Image build command from the Dockerfile to use it in Docker Compose&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker build -t php8.2-fpm-mongo .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where php8.2-fpm-mongo - is the name of the image to be used in docker-compose&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="750" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_799fd9d09db8c744.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_83f93b84ac5c1e4e.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_501faebb2c058f50.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_501faebb2c058f50.webp" alt="Dockerfile for PHM + MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="docker-composeyml-to-test-the-web-app">Docker-compose.yml to test the web app&lt;/h3>
&lt;p>The next step is to create the docker-compose.yml file.
I will add the Nginx web server and my Image with PHP and MongoDB to the Docker-compose file.&lt;/p>
&lt;p>&lt;em>docker-compose.yml&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">version: '3.9'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> web:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: nginx:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - '80:80'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./app:/var/www/html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./config/default.conf:/etc/nginx/conf.d/default.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> php-fpm:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: php8.2-fpm-mongo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./app:/var/www/html&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The application is located in the app/ folder in the same directory as docker-compose.yml.&lt;/p>
&lt;p>Now it will be a very simple index.php script that prints out information about itself.&lt;/p>
&lt;p>Create an app directory and an index.php file.&lt;/p>
&lt;p>&lt;em>app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">phpinfo();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Structure of files and folders&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── index.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── default.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Dockerfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── docker-compose.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You will also notice /config/default.conf. This is the configuration of Nginx for handling requests and running PHP. Here is my example of a default.conf file. Let’s create it too.&lt;/p>
&lt;p>&lt;em>/config/default.conf&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">server {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen 80;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server_name localhost;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> index index.php index.html;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error_log /var/log/nginx/error.log;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> access_log /var/log/nginx/access.log;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> root /var/www/html;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rewrite ^/(.*)/$ /$1 permanent;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location / {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> try_files $uri $uri/ /index.php?$query_string;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location ~ \.php$ {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> try_files $uri =404;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_split_path_info ^(.+\.php)(/.+)$;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_pass php-fpm:9000;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_index index.php;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> include fastcgi_params;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_param PATH_INFO $fastcgi_path_info;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fastcgi_buffering off;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we run docker-compose now, we can open &lt;code>localhost&lt;/code> in the browser and see the running php from the app/index.php file.&lt;/p>
&lt;p>Run docker-compose&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1802" height="268" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_5590e287350f9cb.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_85d91cb158ffb558.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_188e5e3950061801.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_188e5e3950061801.webp" alt="Run docker-compose" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Open localhost in the browser.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1162" height="842" sizes="(max-width: 1162px) 100vw, 1162px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-8_hu_470bc354200817a9.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-8_hu_1050e9756e3327bc.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-8_hu_6cc3230269a09111.webp 1162w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-8_hu_6cc3230269a09111.webp" alt="PHP Info - Localhost - browser" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Stop docker-compose to continue setting up. We haven’t connected MongoDB yet.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker-compose down&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1814" height="392" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_964088a811c2243b.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_326abd557089981b.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_d16deacecd5dd6e6.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_d16deacecd5dd6e6.webp" alt="Stop docker-compose" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="connect-mongodb-to-our-docker-compose">Connect MongoDB to our docker-compose&lt;/h3>
&lt;p>Add the new db service to our docker-compose.yml.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">version: '3.9'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> web:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: nginx:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - '80:80'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./app:/var/www/html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./config/default.conf:/etc/nginx/conf.d/default.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> php-fpm:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: php8.2-fpm-mongo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./app:/var/www/html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DB_USERNAME: root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DB_PASSWORD: secret
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DB_HOST: mongodb # matches the service with mongodb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mongodb:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: "percona/percona-server-mongodb:6.0.4"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # image: "percona/percona-server-mongodb:6.0.4-3-arm64" # For Apple M1/M2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./data:/data/db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MONGO_INITDB_ROOT_USERNAME: root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MONGO_INITDB_ROOT_PASSWORD: secret
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MONGO_INITDB_DATABASE: tutorial
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - "27017:27017"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you examine the changes in the docker-compose.yml file carefully, you will notice:&lt;/p>
&lt;ol>
&lt;li>I add image (db) with Percona Server MongoDB 6.0.4&lt;/li>
&lt;li>I use data/ folder in the same directory as volumes. It’s convenient for me to easily access DB files, transfer them, and examine them locally.&lt;/li>
&lt;li>I pass environment variables to create a MongoDB root user.&lt;/li>
&lt;li>I also added environment variables in php-fpm to use them to connect to the database in the application.&lt;/li>
&lt;li>And the volumes parameter will link our local app directory directly to the container, this will allow us to modify the code and immediately check the result in the browser without restarting the container.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ./app:/var/www/html&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s modify our PHP script to check the operation of the database.&lt;/p>
&lt;h2 id="connecting-to-mongodb-in-the-application">Connecting to MongoDB in the application&lt;/h2>
&lt;h3 id="install-required-php-packages-to-work-with-mongodb">Install required PHP packages to work with MongoDB&lt;/h3>
&lt;p>Create an app/composer.json file to install and use the required MongoDB libraries and extensions for PHP.&lt;/p>
&lt;p>&lt;em>app/composer.json&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "require": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "mongodb/mongodb": "^1.6",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "ext-mongodb": "^1.6"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Connect to the php-fpm container and install the Composer packages&lt;/p>
&lt;p>Run docker-compose&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Look up the name of the container with php-fpm, in my case it is github-php-fpm-1.&lt;/p>
&lt;p>Run the command to connect to the container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">docker exec -it [php-fpm-container] bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run the installation of the Composer packages described in our composer.json file with&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">composer install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1888" height="1168" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_41bf1c526587abb5.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_e27dbb888c33f8b9.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_32f3e8d81464b60d.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_32f3e8d81464b60d.webp" alt="Install required PHP packages to work with MongoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now we can connect to MongoDB in our PHP application.&lt;/p>
&lt;h3 id="connecting-to-mongodb-in-a-php-application">Connecting to MongoDB in a PHP application.&lt;/h3>
&lt;p>Now we slightly modify the index.php script to connect to the database and test data recording.&lt;/p>
&lt;p>&lt;em>/app/index.php&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Enabling Composer Packages
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require __DIR__ . '/vendor/autoload.php';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Get environment variables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$local_conf = getenv();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_USERNAME', $local_conf['DB_USERNAME']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_PASSWORD', $local_conf['DB_PASSWORD']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">define('DB_HOST', $local_conf['DB_HOST']);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Connect to MongoDB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$db_client = new \MongoDB\Client('mongodb://'. DB_USERNAME .':' . DB_PASSWORD . '@'. DB_HOST . ':27017/');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$db = $db_client->selectDatabase('tutorial');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Test insert data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for ($page = 1; $page &lt;= 1000; $page++) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $data = [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'page_id' => $page,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'title' => "Page " . $page,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'date' => date("m.d.y H:i:s"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'timestamp' => time(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'mongodb_time' => new MongoDB\BSON\UTCDateTime(time() * 1000)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $updateResult = $db->pages->updateOne(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'page_id' => $page // query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['$set' => $data],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['upsert' => true]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> echo $page . " " ;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo '&lt;br/>Finish';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">exit;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we run localhost in the browser, our application will write 1,000 documents from the for loop into the database. It will also display the sequential numbers of the documents being written.&lt;/p>
&lt;h2 id="connecting-to-mongodb-via-mongodb-compass">Connecting to MongoDB via MongoDB Compass&lt;/h2>
&lt;p>&lt;a href="https://www.mongodb.com/products/compass" target="_blank" rel="noopener noreferrer">MongoDB Compass&lt;/a> is a handy desktop application to work with MongoDB. I use it to browse databases and collections and create indexes.&lt;/p>
&lt;p>This is a quick way to conveniently look through written data and check errors.&lt;/p>
&lt;p>Let’s connect to the database using MongoDB Compass to check that the data is actually written.&lt;/p>
&lt;p>You need to use Localhost as host and the user/password from docker-compose.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1562" height="1540" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_3c89a060df103306.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_d66d1c0088205ec0.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_4f1f27bb7e0d35d9.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_4f1f27bb7e0d35d9.webp" alt="Connecting to MongoDB via MongoDB Compass" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After connecting you will see 1000 documents written to the database and you can make test queries or add an index.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1318" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_139f21f4e5278a18.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_a7ba215dd95b7eed.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_845257698c6e76af.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_845257698c6e76af.webp" alt="Connecting to MongoDB via MongoDB Compass" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="dont-forget-indexes-mongodb">Don’t forget indexes MongoDB&lt;/h2>
&lt;p>If you write and read data on certain fields, make sure to create indexes on those fields.&lt;/p>
&lt;p>For example, if you make 10,000 records with the script we developed above, you will notice a slow writing speed. It could be 20 seconds. But if you create an index on the page field, the write speed will be reduced by a factor of 10 to 2 seconds.&lt;/p>
&lt;p>Always create indexes.&lt;/p>
&lt;p>This is not hard to do through MongoDB Compass in the Indexes section of the collection.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1652" height="1046" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_fa5fab12b1bfe220.webp 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_286ff15a38e22b53.webp 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_5c93631b2719e8f3.webp 1400w" src="https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_5c93631b2719e8f3.webp" alt="Connecting to MongoDB via MongoDB Compass" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is also easy to do in our PHP app, using the method&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$db->pages->createIndex(['page_id' => 1]);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This will create an index on page_id, because we do insert/upsert with a condition on this field and it is a unique key.&lt;/p>
&lt;p>Add it before the for loop, and increase the number of pages to 10k to compare.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">// Create an index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$db->pages->createIndex(['page_id' => 1]);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Test insert data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for ($page = 1; $page &lt;= 10000; $page++) {&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This will greatly increase the speed at which the script runs.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>We set up an environment and developed a PHP script to work with MongoDB.&lt;/p>
&lt;p>In my opinion, it was simple. All the source code you can see and use from my &lt;a href="https://github.com/dbazhenov/nginx-php-mongodb-docker-compose" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>&lt;/p>
&lt;p>To summarize:&lt;/p>
&lt;ol>
&lt;li>We have now installed standalone &lt;a href="https://www.percona.com/software/mongodb/percona-server-for-mongodb?utm_source=percona-community&amp;utm_medium=blog&amp;utm_campaign=daniil" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> using Docker-compose locally. However, it is recommended to use ReplicaSet with at least one node for production. We will definitely try this on a separate server using AWS as an example.&lt;/li>
&lt;li>For production applications, it is recommended to use ReplicaSet with several nodes. We will definitely do that too.&lt;/li>
&lt;li>We will install PMM to monitor database, see how our script loads the database, and see database queries with QAN and other PMM features.&lt;/li>
&lt;/ol>
&lt;p>In the next posts, I will work on improving the application. I will focus on database customization. You will learn how to improve the application so that it brings practical use, gets data from the GitHub API, and writes to the database. We’ll divide the application into console scripts and Web.&lt;/p>
&lt;p>If you are interested in learning more about the PHP application, write in a comment or on the &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">forum&lt;/a>.&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>MongoDB</category><category>Databases</category><category>Percona</category><category>PHP</category><category>Docker</category><media:thumbnail url="https://percona.community/blog/2023/03/PHP-1_hu_42b5ad181a7edbf6.jpg"/><media:content url="https://percona.community/blog/2023/03/PHP-1_hu_f283ee59e2d55cf3.jpg" medium="image"/></item><item><title>Some Notable Bugfixes in MySQL 8.0.32</title><link>https://percona.community/blog/2023/03/15/some-notable-bugfixes-in-mysql-8.0.32/</link><guid>https://percona.community/blog/2023/03/15/some-notable-bugfixes-in-mysql-8.0.32/</guid><pubDate>Wed, 15 Mar 2023 00:00:00 UTC</pubDate><description>MySQL 8.0.32 came out recently and had some important bugfixes contributed by Perconians. Here is a brief overview of the work done.</description><content:encoded>&lt;p>MySQL 8.0.32 came out recently and had some important bugfixes contributed by Perconians. Here is a brief overview of the work done.&lt;/p>
&lt;h2 id="inconsistent-data-and-gtids-with-mysqldump">Inconsistent data and GTIDs with mysqldump&lt;/h2>
&lt;p>Marcelo Altmann (Senior Software Engineer) fixed the bug when data and GTIDs backed up by mysqldump were inconsistent. It happened when the options –single-transaction and –set-gtid-purged=ON were both used because GTIDs on the server could have already increased between the start of the transaction by mysqldump and the fetching of GTID_EXECUTED. Marcelo developed a patch, and it was partially included in the release. Now, in MySQL 8.0.32, a FLUSH TABLES WITH READ LOCK is performed before fetching GTID_EXECUTED, to ensure its value is consistent with the snapshot taken by mysqldump. However, Percona Server for MySQL includes the entire patch, which does not require FLUSH TABLE WITH READ LOCK to work.&lt;/p>
&lt;p>Marcelo also corrected the issue when the MySQL server &lt;a href="https://perconadev.atlassian.net/browse/PS-8303" target="_blank" rel="noopener noreferrer">exits on ALTER TABLE created an assertion failure: dict0mem.h:2498:pos &lt; n_def&lt;/a>.&lt;/p>
&lt;h2 id="fixing-garbled-utf-characters">Fixing garbled UTF characters&lt;/h2>
&lt;p>Kamil Holubicki (Senior Software Engineer) proposed a patch to fix garbled UTF characters in SHOW ENGINE INNODB STATUS. It happened because the string was truncated, and UTF characters (which are multibyte) were cut in the middle which caused garbage at the end of the string.&lt;/p>
&lt;h2 id="duplicate-table-space-objects-in-56-to-80-upgrade">Duplicate table space objects in 5.6 to 8.0 upgrade&lt;/h2>
&lt;p>Rahul Malik (Software Engineer) investigated and fixed an issue when an 8.0 upgrade from MySQL 5.6 crashed with Assertion failure. It happened due to a duplicate table space object. All SYS_* tables are loaded, and then their table IDs are changed. Some SYS tables like SYS_ZIP_DICT, VIRTUAL can have ids > 1024 (say, 1028).&lt;/p>
&lt;p>Changing table_ids of SYS_FIELDS from 4 to 1028 will conflict with the table_ids of those existing SYS_ZIP_DICT/VIRTUAL which haven’t been shifted by 1024 yet and are currently loaded with 1028. Hence, we need to change the IDs of that SYS tables in reverse order to fix it. So in the example above, SYS_FIELD is the first shift to 1028+1024, and then SYS_FIELD changes to 1028 to avoid conflicts.&lt;/p>
&lt;h2 id="why-open-source-databases-matter">Why open source databases matter&lt;/h2>
&lt;p>Great work by Marcelo, Kamil, Rahul, and everybody else who contributed to the MySQL 8.0.32 release.&lt;/p>
&lt;p>This is why open source databases are so important. We can all help improve MySQL, and those improvements benefit all users of MySQL.&lt;/p>
&lt;p>Percona is proud to be part of the MySQL community, and we hope you’ll join us in improving MySQL and its surrounding software. Check out our &lt;a href="https://percona.community/contribute/" target="_blank" rel="noopener noreferrer">contributing page&lt;/a> to find ways to contribute!&lt;/p></content:encoded><author>Aleksandra Abramova</author><category>MySQL</category><category>Databases</category><category>Open Source</category><category>Release</category><media:thumbnail url="https://percona.community/blog/2023/03/mysql-bugfixes_hu_3963a5ecfa010f5.jpg"/><media:content url="https://percona.community/blog/2023/03/mysql-bugfixes_hu_1fd86d064829157f.jpg" medium="image"/></item><item><title>Using the JSON data type with MySQL 8</title><link>https://percona.community/blog/2023/03/13/using-the-json-data-type-with-mysql-8/</link><guid>https://percona.community/blog/2023/03/13/using-the-json-data-type-with-mysql-8/</guid><pubDate>Mon, 13 Mar 2023 00:00:00 UTC</pubDate><description>If you are a mobile app, frontend, backend, or game developer, you use data types such as string, numeric, or DateTime. You also know that since the advent of non-relational databases (NoSQL) such as MongoDB, which, by not being tied to a traditional SQL schema, do reading and writing on databases much faster. But MySQL showed that storing the JSON (JavaScript Object Notation) data type could also improve the speed of reading and writing relational databases.</description><content:encoded>&lt;p>If you are a mobile app, frontend, backend, or game developer, you use data types such as string, numeric, or DateTime. You also know that since the advent of non-relational databases (NoSQL) such as &lt;strong>MongoDB&lt;/strong>, which, by not being tied to a traditional &lt;strong>SQL schema&lt;/strong>, do reading and writing on databases much faster. But &lt;strong>MySQL&lt;/strong> showed that storing the JSON (JavaScript Object Notation) data type could also improve the speed of reading and writing relational databases.&lt;/p>
&lt;p>This post will explore the JSON Data type in &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a>.&lt;/p>
&lt;p>One of the key features of &lt;strong>Percona Server&lt;/strong> is support for &lt;strong>JSON&lt;/strong> data type, which allows for the storage of JSON documents within MySQL. It allows for more flexible and efficient storage of semi-structured data (​​which is more human-readable ) within a relational database.&lt;/p>
&lt;p>We will install &lt;strong>Percona Server for MySQL&lt;/strong> in a Docker container to make basic operations for inserting, modifying, and removing JSON data types.&lt;/p>
&lt;p>To start, we will bring version 8.0 of Percona Server for MySQL; the name of this image in Docker Hub is percona-server. You will need Docker; if you don’t have it installed, follow the official &lt;a href="https://docs.docker.com/engine/install/" target="_blank" rel="noopener noreferrer">Docker documentation&lt;/a>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker pull percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We will run the container for &lt;strong>Percona Server for MySQL&lt;/strong>, call our container percona-server and pass in an environment variable called &lt;strong>MYSQL_ROOT_PASSWORD&lt;/strong>; This variable specifies a password that is set for the MySQL root account.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --name percona-server &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>root &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After confirming that our container is running with “docker ps,” we can enter our Percona Server for MySQL container to start executing commands.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-server /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The Percona Server for MySQL database is already running, and we will proceed to connect to it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mysql -uroot -p&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Use &lt;strong>root&lt;/strong> as a password.&lt;/p>
&lt;p>Create the database called &lt;strong>cinema&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE DATABASE library&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">USE library&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a table called &lt;strong>books&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE TABLE books &lt;span class="o">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> book_id BIGINT PRIMARY KEY AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> title VARCHAR&lt;span class="o">(&lt;/span>100&lt;span class="o">)&lt;/span> UNIQUE NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> publisher VARCHAR&lt;span class="o">(&lt;/span>100&lt;span class="o">)&lt;/span> NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> labels JSON NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">)&lt;/span> &lt;span class="nv">ENGINE&lt;/span> &lt;span class="o">=&lt;/span> InnoDB&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="insert-json-type-into-books-table">Insert JSON type into books table&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">INSERT INTO books&lt;span class="o">(&lt;/span>title,publisher, labels&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">VALUES&lt;span class="o">(&lt;/span>&lt;span class="s1">'Green House'&lt;/span>, &lt;span class="s1">'Joe Monter'&lt;/span>, &lt;span class="s1">'{"about" : {"gender": "action", "cool": true, "notes": "labeled"}}'&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO books&lt;span class="o">(&lt;/span>title,publisher, labels&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">VALUES&lt;span class="o">(&lt;/span>&lt;span class="s1">'El camino'&lt;/span>, &lt;span class="s1">'Daniil Zotl'&lt;/span>, &lt;span class="s1">'{"about" : {"gender": "documental", "cool": true, "notes": "labeled"}}'&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span> * from books&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see, JSON is a more flexible data type than what you might be used to when working with data in &lt;strong>MySQL&lt;/strong>.&lt;/p>
&lt;h2 id="select-with-json_extract">Select with JSON_EXTRACT&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">SELECT title, JSON_EXTRACT&lt;span class="o">(&lt;/span>labels, &lt;span class="s1">'$.about.notes'&lt;/span>&lt;span class="o">)&lt;/span> AS Notes FROM books&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A shortcut of JSON_EXTRACT is “->”
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT title, labels->&lt;span class="s1">'$.about.notes'&lt;/span> AS Notes FROM books&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The short operator -> provides the same functionality as JSON_EXTRACT&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">SELECT titulo, etiquetas->&lt;span class="s1">'$.acerca.genero'&lt;/span> AS Genero FROM books&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="updating-json-type-records">Updating JSON type records&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">UPDATE books SET &lt;span class="nv">labels&lt;/span> &lt;span class="o">=&lt;/span> JSON_REPLACE&lt;span class="o">(&lt;/span>labels, &lt;span class="s1">'$.about.gender'&lt;/span>, &lt;span class="s1">'romance'&lt;/span>&lt;span class="o">)&lt;/span> WHERE &lt;span class="nv">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'the roses'&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">UPDATE books SET &lt;span class="nv">labels&lt;/span> &lt;span class="o">=&lt;/span> JSON_REPLACE&lt;span class="o">(&lt;/span>labels, &lt;span class="s1">'$.about.notes'&lt;/span>, &lt;span class="s1">'not labeled'&lt;/span>&lt;span class="o">)&lt;/span> WHERE &lt;span class="nv">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'the roses'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span> * from books&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="deleting-a-json-record">Deleting a JSON record&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">DELETE FROM books WHERE &lt;span class="nv">book_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> AND JSON_EXTRACT&lt;span class="o">(&lt;/span>labels, &lt;span class="s1">'$.about.gender'&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">"documental"&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="deleting-a-value-inside-a-json-structure">Deleting a value inside a JSON structure&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">UPDATE books SET &lt;span class="nv">labels&lt;/span> &lt;span class="o">=&lt;/span> JSON_REMOVE&lt;span class="o">(&lt;/span>labels, &lt;span class="s1">'$.about.notes'&lt;/span>&lt;span class="o">)&lt;/span> WHERE &lt;span class="nv">book_id&lt;/span> &lt;span class="o">=&lt;/span> 2&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can use these fundamental operations to manage JSON data types in Percona Server MySQL. This allows for more flexible and efficient data modeling and querying for applications that work with JSON data. How will that work in an application? Keep an eye out, I’ll be following this up with a blog about an application using JSON data in MySQL very soon.&lt;/p>
&lt;p>Get more about Percona Server for MySQL documentation in our &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">official documentation&lt;/a>. And if you want to know why JSON is the preferred format for many developers and why it’s so popular, check out &lt;a href="https://www.percona.com/blog/json-and-relational-databases-part-one" target="_blank" rel="noopener noreferrer">David Stokes’ blog: JSON and Relational Databases – Part One&lt;/a>&lt;/p></content:encoded><author>Edith Puclla</author><category>JSON</category><category>MySQL</category><category>Databases</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2023/03/13-cover-change_hu_828a39cdc2af9b2a.jpg"/><media:content url="https://percona.community/blog/2023/03/13-cover-change_hu_ad37fbc7e77a97a0.jpg" medium="image"/></item><item><title>Backups for MySQL With mysqldump</title><link>https://percona.community/blog/2023/03/10/backups-for-mysql-with-mysqldump/</link><guid>https://percona.community/blog/2023/03/10/backups-for-mysql-with-mysqldump/</guid><pubDate>Fri, 10 Mar 2023 00:00:00 UTC</pubDate><description>Basic Usage mysqldump is a client utility that can be used for doing logical backups. It will generate the necessary SQL statements to reproduce the original database.</description><content:encoded>&lt;h2 id="basic-usage">Basic Usage&lt;/h2>
&lt;p>&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html" target="_blank" rel="noopener noreferrer">mysqldump&lt;/a> is a client utility that can be used for doing logical backups. It will generate the necessary SQL statements to reproduce the original database.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1200" height="799" sizes="(max-width: 1200px) 100vw, 1200px" srcset="https://percona.community/blog/2023/03/backup_hu_57edb02d454db55c.webp 768w, https://percona.community/blog/2023/03/backup_hu_1cb4869670b61f25.webp 480w, https://percona.community/blog/2023/03/backup_hu_c818de712ebc5b3a.webp 1200w" src="https://percona.community/blog/2023/03/backup_hu_c818de712ebc5b3a.webp" alt="Backup" loading="lazy" />&lt;figcaption>Backup by Nick Youngson CC BY-SA 3.0 Pix4free&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>The following statements are some common uses of mysqldump:&lt;/p>
&lt;ol>
&lt;li>&lt;code>mysqldump -u username -p database_name [table_name] > dump.sql&lt;/code>&lt;/li>
&lt;li>&lt;code>mysqldump -u username -p --databases db1_name db2_name > dump.sql&lt;/code>&lt;/li>
&lt;li>&lt;code>mysqldump -u username -p --all-databases > dump.sql&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>The first example is for backing up a single database. If you need to back up some specific tables instead of the whole database, write their names, space-separated.&lt;/p>
&lt;p>With the &lt;code>--databases&lt;/code> option, you can back up two or more databases, their names must be space separated.&lt;/p>
&lt;p>To back up all the databases in your MySQL server, just append the &lt;code>--all-databases&lt;/code> option.&lt;/p>
&lt;p>The &lt;code>dump.sql&lt;/code> file doesn’t contain the create database SQL statement. If you need it, add it with the &lt;code>-B&lt;/code> option. This is unnecessary if you’re running &lt;code>mysqldump&lt;/code> with the &lt;code>--databases&lt;/code> and &lt;code>--all-databases&lt;/code> options.&lt;/p>
&lt;p>Ignoring tables when backing up a database is also possible with the &lt;code>--ignore-tables&lt;/code> option.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u username -p database_name --ignore-tables=database_name.table1 > database_name.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you need to ignore more than one database, just use the option as many times as needed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u root -p database_name --ignore-table=database_name.table1 --ignore-table=database_name.table2 > database_name.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="schema-backup">Schema Backup&lt;/h2>
&lt;p>In case you need to backup only the schema of your database with no data, run mysqldump with the &lt;code>--no-data&lt;/code> option:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u username -p database_name --no-data > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can also backup the schema while running &lt;code>mysqldump&lt;/code> with the &lt;code>--databases&lt;/code> and &lt;code>--all-databases&lt;/code> options.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u username -p --all-databases --no-data > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u username -p --databases db1_name db2_name --no-data > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="data-restore">Data Restore&lt;/h2>
&lt;p>To restore the databases in your &lt;code>dump.sql&lt;/code> file, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u root -p &lt; dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you need to restore a single database from the complete backup, you can do it by running any of the following statements:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u root -p -o database_name &lt; dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u root -p --one-database database_name &lt; dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In both cases, the database must exist in your MySQL server, as it only will restore the schema and the data.&lt;/p>
&lt;h2 id="conditional-backup">Conditional Backup&lt;/h2>
&lt;p>If you need to create a backup that contains data that matches a condition, you can use a &lt;code>WHERE&lt;/code> clause with mysqldump.&lt;/p>
&lt;p>You can use a single where condition:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump database_name table_name --where="id > 500" > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or multiple conditions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump database_name users --where="id > 500 and disabled = 0" > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As explained &lt;a href="https://mysqldump.guru/how-to-use-a-where-clause-with-mysqldump.html" target="_blank" rel="noopener noreferrer">here&lt;/a> in the &lt;a href="https://mysqldump.guru/" target="_blank" rel="noopener noreferrer">mysqldump.guru&lt;/a> website.&lt;/p>
&lt;p>For example, in a database with the following schema, built from the &lt;a href="https://movienet.github.io/" target="_blank" rel="noopener noreferrer">Movienet&lt;/a> dataset:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="730" height="752" sizes="(max-width: 730px) 100vw, 730px" srcset="https://percona.community/blog/2023/03/movienet_model_hu_43c4404aac1f95d6.webp 480w, https://percona.community/blog/2023/03/movienet_model_hu_3ed23a72526d5937.webp 730w" src="https://percona.community/blog/2023/03/movienet_model_hu_3ed23a72526d5937.webp" alt="Movienet Database" loading="lazy" />&lt;figcaption>Movienet Database&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>If you want to back up the movies produced in a specific country, like Mexico, a way to do it is by running mysqldump with a &lt;code>WHERE&lt;/code> clause.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mysqldump -u root -p movienet movies --where=”country = 22” > dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>22&lt;/code> is the &lt;code>country_id&lt;/code> of Mexico in this particular database, created using &lt;a href="https://github.com/mattdark/json-mysql-importer" target="_blank" rel="noopener noreferrer">this Python script&lt;/a>.&lt;/p>
&lt;p>You can also get those values by executing the following SQL statement:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">select movies.movie_id, movies.title, countries.name as country from movies inner join countries on movies.country = countrie
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">s.country_id and movies.country = '22';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+-----------+-----------------------------------------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| movie_id | title | country |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+-----------------------------------------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0047501 | Sitting Bull (1954) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0049046 | Canasta de cuentos mexicanos (1956) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0076336 | Hell Without Limits (1978) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0082048 | El barrendero (1982) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0082080 | Blanca Nieves y sus 7 amantes (1980) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0083057 | El sexo de los pobres (1983) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0110185 | El jardín del Edén (1994) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0116043 | De jazmín en flor (1996) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0121322 | El giro, el pinto, y el Colorado (1979) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0133354 | Algunas nubes (1995) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0207055 | La risa en vacaciones 4 (TV Movie 1994) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0208889 | To and Fro (2000) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0211878 | La usurpadora (TV Series 1998– ) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0220306 | El amarrador 3 (1995) | Mexico |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| tt0229008 | El vampiro teporocho (1989) | Mexico |&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="skipping-databases">Skipping Databases&lt;/h2>
&lt;p>There’s no option for &lt;code>mysqldump&lt;/code> to skip databases when generating the backup, but here’s a solution that could work for you:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DATABASES_TO_EXCLUDE="db1 db2 db3"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EXCLUSION_LIST="'information_schema','mysql'"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for DB in `echo "${DATABASES_TO_EXCLUDE}"`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> EXCLUSION_LIST="${EXCLUSION_LIST},'${DB}'"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">done
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SQLSTMT="SELECT schema_name FROM information_schema.schemata"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SQLSTMT="${SQLSTMT} WHERE schema_name NOT IN (${EXCLUSION_LIST})"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MYSQLDUMP_DATABASES="--databases"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for DB in `mysql -u username -p -ANe"${SQLSTMT}"`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MYSQLDUMP_DATABASES="${MYSQLDUMP_DATABASES} ${DB}"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">done
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MYSQLDUMP_OPTIONS="--routines --triggers"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysqldump -u username -p ${MYSQLDUMP_OPTIONS} ${MYSQLDUMP_DATABASES} > MySQLDatabases.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The above BASH script will generate the backup of your MySQL server excluding the &lt;code>information_schema&lt;/code> and &lt;code>mysql&lt;/code> databases, listed in the &lt;code>EXCLUSION_LIST&lt;/code> variable, as well as the databases of your choice in the &lt;code>DATABASES_TO_EXCLUDE&lt;/code> variable.&lt;/p>
&lt;p>Don’t forget to add the databases you want to exclude to the &lt;code>DATABASES_TO_EXCLUDE&lt;/code> variable, replace the &lt;code>username&lt;/code>, in both &lt;code>mysql&lt;/code> and &lt;code>mysqldump&lt;/code> commands, and add the required options to the &lt;code>MYSQLDUMP_OPTIONS&lt;/code> variable.&lt;/p>
&lt;h2 id="security-considerations">Security Considerations&lt;/h2>
&lt;p>Some of the common questions in &lt;a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">our forum&lt;/a> are about how to do a partial restoration from a complete backup. For example, when you back up a database with &lt;code>mysqldump&lt;/code>, you will get the statements for creating the schema of the database and inserting the data from your backup.&lt;/p>
&lt;p>If you only need the schema, you can run mysqldump with the –no-data option. But if you need to restore the schema of a specific database from a complete backup, I found an interesting solution:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cat dump.sql | grep -v ^INSERT | mysql -u username -p&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The above command will restore the schema of your database, skipping the SQL statements for inserting the data. It works well when you backup a single database, but there’s no reason to use it as you can get the schema with the &lt;code>--no-data&lt;/code> option, instead of removing the inserts.&lt;/p>
&lt;p>What happens if you try to run this command with a backup that includes all the databases in your server? You must be careful as this will try to overwrite the system schema in the &lt;code>mysql&lt;/code> database which is dangerous. This database store authentication details and overriding the data will make you lose access to your server.&lt;/p>
&lt;p>If you don’t need to backup the &lt;code>mysql&lt;/code> database, run &lt;code>mysqldump&lt;/code> with the &lt;code>--databases&lt;/code> option to specify which databases you require or use the script shared in the &lt;a href="#skipping-databases">Skipping Databases&lt;/a> section.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Through this blog post you learned how to use mysqldump for backing up the databases in your MySQL server as well as some recommendations while using this tool. For advanced usage of mysqldump you can check &lt;a href="https://www.percona.com/blog/the-mysqlpump-utility/" target="_blank" rel="noopener noreferrer">this article&lt;/a> in our blog.&lt;/p></content:encoded><author>Mario García</author><category>MySQL</category><category>Backup</category><media:thumbnail url="https://percona.community/blog/2023/03/backup_hu_aac33fbd4cc33f69.jpg"/><media:content url="https://percona.community/blog/2023/03/backup_hu_8f2cb2ce79c4a147.jpg" medium="image"/></item><item><title>Monitor your databases with Open Source tools like PMM</title><link>https://percona.community/blog/2023/03/06/monitor-your-databases-with-open-source-tools-like-pmm/</link><guid>https://percona.community/blog/2023/03/06/monitor-your-databases-with-open-source-tools-like-pmm/</guid><pubDate>Mon, 06 Mar 2023 00:00:00 UTC</pubDate><description>In this post, we will cover the value of database monitoring and how we can use Open Source tools like PMM Percona Monitoring and Management to monitor and manage databases effectively.</description><content:encoded>&lt;p>In this post, we will cover the value of database monitoring and how we can use Open Source tools like &lt;strong>PMM&lt;/strong> &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management&lt;/a> to monitor and manage databases effectively.&lt;/p>
&lt;h2 id="why-should-i-care-about-database-monitoring">Why should I care about database monitoring?&lt;/h2>
&lt;p>Once you have passed the installation and configuration of your databases and it is well underway, you have to start monitoring it, and not only the database but the elements related to it.&lt;/p>
&lt;p>Questions like these will begin to arise:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Is my database performing well?&lt;/p>
&lt;ul>
&lt;li>Are query response times consistently slow?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Is my database available and accepting connections?&lt;/p>
&lt;ul>
&lt;li>Connections to the database close to the maximum limit&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Is my system stable?&lt;/p>
&lt;ul>
&lt;li>How about CPU, memory, and disk?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Am I experiencing avoidable downtime?&lt;/p>
&lt;ul>
&lt;li>Hardware failures, network outages.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;ul>
&lt;li>Am I experiencing data loss?
&lt;ul>
&lt;li>Disk crashes&lt;/li>
&lt;li>Human errors&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Am I minimizing performance issues that can impact my business?&lt;/li>
&lt;li>Can I quickly identify and resolve issues before they become more significant problems?&lt;/li>
&lt;/ul>
&lt;p>To answer these questions, you will need to find tools that let you keep your database monitored, and you can opt for free tools for monitoring. &lt;strong>PMM&lt;/strong> is one of them, which is entirely open source.&lt;/p>
&lt;h2 id="percona-monitoring-and-management-pmm">Percona Monitoring and Management (PMM)&lt;/h2>
&lt;p>&lt;strong>PMM&lt;/strong> is an open source database observability, monitoring, and management tool for:&lt;/p>
&lt;ul>
&lt;li>MySQL&lt;/li>
&lt;li>MariaDB&lt;/li>
&lt;li>PostgreSQL&lt;/li>
&lt;li>MongoDB&lt;/li>
&lt;li>And others&lt;/li>
&lt;/ul>
&lt;p>It can also help to improve the performance of your databases, simplify their management and strengthen their security.&lt;/p>
&lt;p>&lt;strong>PMM&lt;/strong> is built on top of open source software&lt;/p>
&lt;ul>
&lt;li>Grafana&lt;/li>
&lt;li>VictoriaMetrics/Prometheus&lt;/li>
&lt;li>ClickHouse&lt;/li>
&lt;li>PostgreSQL&lt;/li>
&lt;li>Docker&lt;/li>
&lt;/ul>
&lt;h2 id="pmm-interface">PMM Interface&lt;/h2>
&lt;p>There are three levels of depth:&lt;/p>
&lt;ul>
&lt;li>Dashboards&lt;/li>
&lt;li>Graphs&lt;/li>
&lt;li>Metrics&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1374" height="770" sizes="(max-width: 1374px) 100vw, 1374px" srcset="https://percona.community/blog/2023/03/01-interface_hu_16cdf479d1f7b017.webp 768w, https://percona.community/blog/2023/03/01-interface_hu_ed8d6f18db8eaf32.webp 480w, https://percona.community/blog/2023/03/01-interface_hu_1772625e18eec965.webp 1374w" src="https://percona.community/blog/2023/03/01-interface_hu_1772625e18eec965.webp" alt="Interface" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="metrics--database-monitoring">Metrics &amp; Database Monitoring&lt;/h2>
&lt;p>Important database metrics you should monitor:&lt;/p>
&lt;ul>
&lt;li>It will depend on your specific database and use case&lt;/li>
&lt;li>Monitor the metrics that are relevant to your database and your business&lt;/li>
&lt;li>You should have alerts and monitoring processes to ensure you are aware of any problems as they occur or before&lt;/li>
&lt;/ul>
&lt;p>Some important metrics that could indicate potential database issues:&lt;/p>
&lt;ul>
&lt;li>Query performance&lt;/li>
&lt;li>High CPU utilization&lt;/li>
&lt;li>High Memory usage&lt;/li>
&lt;li>High Disk I/O&lt;/li>
&lt;li>User Connection&lt;/li>
&lt;li>Data growth&lt;/li>
&lt;li>Others&lt;/li>
&lt;/ul>
&lt;p>Let’s analyze each of them, and they will also answer your questions at the beginning.&lt;/p>
&lt;h3 id="long-query-response-times">Long Query Response Times&lt;/h3>
&lt;p>&lt;strong>PMM&lt;/strong> helps you monitor the performance of individual queries and identify slow-performing queries that need to be optimized.
We can use &lt;a href="https://docs.percona.com/percona-monitoring-and-management/get-started/query-analytics.html" target="_blank" rel="noopener noreferrer">Query Analytics in PMM&lt;/a> to visualize all the queries running in our database; we can inspect each of them and see which is the one sending more queries per second and much longer it takes to execute it. Also, &lt;strong>PMM&lt;/strong> will show you suggestions to fix or improve queries.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1940" height="936" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/02-long-query-response_hu_23ae5ed5525af5bf.webp 768w, https://percona.community/blog/2023/03/02-long-query-response_hu_2ae586547119ddc9.webp 480w, https://percona.community/blog/2023/03/02-long-query-response_hu_58148eb0d179a2f.webp 1400w" src="https://percona.community/blog/2023/03/02-long-query-response_hu_58148eb0d179a2f.webp" alt="Long query Response" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="high-cpu-utilization">High CPU Utilization&lt;/h3>
&lt;p>&lt;strong>PMM&lt;/strong> helps you monitor the number of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/dashboards/dashboard-cpu-utilization-details.html" target="_blank" rel="noopener noreferrer">CPU resources&lt;/a> the database uses and identify performance bottlenecks.&lt;/p>
&lt;p>In the section on CPU utilization, you will see how much of your CPU is being used in a period of time. This is very useful when you need to increase your resources.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1944" height="962" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_602a105255bd9f2.webp 768w, https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_22329673ab58bd95.webp 480w, https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_f514025c830c0244.webp 1400w" src="https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_f514025c830c0244.webp" alt="High Cpu Utilization" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="high-memory-usage">High Memory usage&lt;/h3>
&lt;p>&lt;strong>PMM&lt;/strong> helps you &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/dashboards/dashboard-memory-details.html" target="_blank" rel="noopener noreferrer">monitor the amount of memory&lt;/a> being used by the database and determine if you need to add more memory or optimize your database configuration.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1904" height="960" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/04-high-memory-usage_hu_628adbd2e168aca2.webp 768w, https://percona.community/blog/2023/03/04-high-memory-usage_hu_95a0542e8ff70e7f.webp 480w, https://percona.community/blog/2023/03/04-high-memory-usage_hu_bee63db927c9c817.webp 1400w" src="https://percona.community/blog/2023/03/04-high-memory-usage_hu_bee63db927c9c817.webp" alt="High Memory Usage" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="disk-io">Disk I/O&lt;/h3>
&lt;p>PMM helps you monitor the number of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/dashboards/dashboard-disk-details.html" target="_blank" rel="noopener noreferrer">disk I/O operations&lt;/a> performed by the database and identify any potential performance bottlenecks. See here the panel of Disk IO Latency!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2006" height="1004" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/05-disk-io_hu_66604dafcb986c4f.webp 768w, https://percona.community/blog/2023/03/05-disk-io_hu_3723db70ccd3eab9.webp 480w, https://percona.community/blog/2023/03/05-disk-io_hu_9e4e0b8c4b8b6dcb.webp 1400w" src="https://percona.community/blog/2023/03/05-disk-io_hu_9e4e0b8c4b8b6dcb.webp" alt="Disk Io" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="user-connections">User connections&lt;/h3>
&lt;p>&lt;strong>PMM&lt;/strong> helps you monitor the number of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/dashboards/dashboard-mysql-user-details.html" target="_blank" rel="noopener noreferrer">active database connections&lt;/a> and determine if your user connection is sized appropriately. If you limit the number of users that should connect to your database, this panel will show you when you are reaching that limit so that you can increase the number.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1736" height="1012" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/06-user-conexion_hu_60d2366b5d25fdab.webp 768w, https://percona.community/blog/2023/03/06-user-conexion_hu_bf4e25481859bd5d.webp 480w, https://percona.community/blog/2023/03/06-user-conexion_hu_8d57901f3243d487.webp 1400w" src="https://percona.community/blog/2023/03/06-user-conexion_hu_8d57901f3243d487.webp" alt="User Conexion" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="data-growth">Data growth&lt;/h3>
&lt;p>PMM helps you monitor &lt;a href="https://docs.percona.com/percona-monitoring-and-management/details/dashboards/dashboard-mysql-table-details.html" target="_blank" rel="noopener noreferrer">your database growth&lt;/a> over time and plan for capacity and performance needs. This dashboard helps to see the time period in which your database is growing and to be able to learn about performance issues or issues as they occur.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1696" height="970" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/03/07-data-grown_hu_af71366ae07151a4.webp 768w, https://percona.community/blog/2023/03/07-data-grown_hu_1786ea0abef0c3ea.webp 480w, https://percona.community/blog/2023/03/07-data-grown_hu_a3da09ecb2703c4e.webp 1400w" src="https://percona.community/blog/2023/03/07-data-grown_hu_a3da09ecb2703c4e.webp" alt="Data Grown" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="summary">Summary&lt;/h3>
&lt;p>We see the importance of monitoring databases and how to explore PMM for some essential metrics to detect issues and prevent them on time.&lt;/p>
&lt;p>Want to try PMM? We have a &lt;a href="https://pmmdemo.percona.com/graph/" target="_blank" rel="noopener noreferrer">test environment to try PMM&lt;/a> without having to install it first. Feel free to play with it and see how PMM works. If you like it, you can &lt;a href="https://www.percona.com/software/pmm/quickstart" target="_blank" rel="noopener noreferrer">install PMM quickly and start using it in your own environment&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>Monitor</category><category>PMM</category><category>Databases</category><category>Open Source</category><media:thumbnail url="https://percona.community/blog/2023/03/00-moni-cover_hu_bac40403f6d5c7ee.jpg"/><media:content url="https://percona.community/blog/2023/03/00-moni-cover_hu_723ac9c5f153c5a1.jpg" medium="image"/></item><item><title>How to test code blocks in documentation</title><link>https://percona.community/blog/2023/02/28/doc-testing/</link><guid>https://percona.community/blog/2023/02/28/doc-testing/</guid><pubDate>Tue, 28 Feb 2023 00:00:00 UTC</pubDate><description>As any developer, I don’t like to write documentation. But if I am writing it, I would like to test that what I wrote works. I often found myself copy-pasting something from documentation and trying to run it in the terminal (commands, files, etc.), and it didn’t work.</description><content:encoded>&lt;p>As any developer, I don’t like to write documentation. But if I am writing it, I would like to test that what I wrote works.
I often found myself copy-pasting something from documentation and trying to run it in the terminal (commands, files, etc.), and it didn’t work.&lt;/p>
&lt;p>There are usually some environment, typos, or even wrong commands in the doc (that people copy-pasted from the wrong place).&lt;/p>
&lt;p>I know that issue, and after writing the documentation, I usually try to clean up everything in my environment and test the doc. I am reading it and executing commands as they wrote. Sometimes I find something needs to be fixed. So I needed to find a way to test it quickly.&lt;/p>
&lt;p>For example, recent &lt;a href="https://github.com/percona/pmm-doc/blob/main/docs/setting-up/server/podman.md" target="_blank" rel="noopener noreferrer">Podman&lt;/a> doc has both code and files:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> You can override the environment variables by defining them in the file `~/.config/pmm-server/env`. For example, to override the path to a custom registry `~/.config/pmm-server/env`:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ```sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mkdir -p ~/.config/pmm-server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cat &lt;&lt; "EOF" > ~/.config/pmm-server/env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PMM_TAG=2.35.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PMM_IMAGE=docker.io/percona/pmm-server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PMM_PUBLIC_PORT=8443
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ```
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> !!! caution alert alert-warning "Important"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ensure that you modify PMM_TAG in `~/.config/pmm-server/env` and update it regularly as Percona cannot update it. It needs to be done by you.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. Enable and Start.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ```sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> systemctl --user enable --now pmm-server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ```&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Documentation ages and there could be new images that wouldn’t work with this documentation anymore.
Another issue is making changes to the existing documentation - how to know that there are no regressions with something new added or with some fixes?&lt;/p>
&lt;p>Usually, to mitigate those issues, developers and/or tech writers are [re]checking everything manually.&lt;/p>
&lt;p>There are many different automatic approaches to mitigate that issue. The ultimate solution for this is probably &lt;a href="https://orgmode.org/" target="_blank" rel="noopener noreferrer">GNU Emacs Org Mode&lt;/a>. I dream that one day I will learn Emacs and Org Mode.&lt;/p>
&lt;p>But I need a solution now, and the team process is to have documentation in a Markdown format and track everything in GitHub - &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">PMM Documentation&lt;/a>. &lt;a href="#github">GitHub&lt;/a> supports quite good code formating, and it is easy to develop and review Markdown there.&lt;/p>
&lt;p>There are probably &lt;a href="#frameworks">frameworks&lt;/a> that could help with that, but I needed something quick and didn’t want to introduce yet another testing framework to the batch.&lt;/p>
&lt;p>So I was looking for something that would allow me to quickly cut code snippets from the documentation and run them in a GitHub action. While searching, I found this doc blog: &lt;a href="https://tomlankhorst.nl/testing-code-in-markdown-doc-md-github" target="_blank" rel="noopener noreferrer">Test codeblocks in markdown documents&lt;/a>. Which was exactly what I needed :)&lt;/p>
&lt;p>The only problem I found out quickly is that I need something else. Pandoc builds AST tree just fine:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">json&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"t"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"CodeBlock"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"c"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">""&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"sh"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"mkdir -p ~/.config/pmm-server/\ncat &lt;&lt; \"EOF\" > ~/.config/pmm-server/env\nPMM_TAG=2.35.0\nPMM_IMAGE=docker.io/percona/pmm-server\nPMM_PUBLIC_PORT=8443\nEOF"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;span class="err">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"t"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"CodeBlock"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"c"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">""&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"sh"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"systemctl --user enable --now pmm-server"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"t"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">"CodeBlock"&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">"c"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">""&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"sh"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"#first pull can take time\nsleep 80\ntimeout 60 podman wait --condition=running pmm-server"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you see, the 3rd &lt;code>CodeBlock&lt;/code> is not on the same level. So when using &lt;code>jq&lt;/code> approach (from the blog post):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pandoc -i podman.md -t json | jq -r -c '.blocks[] | select(.t | contains("CodeBlock"))? | .c'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[["",[],[]],"```sh\npodman exec -it pmm-server \\\ncurl -ku admin:admin https://localhost/v1/version\n```"]p'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It returns only the block from that level that &lt;code>jq&lt;/code> program specifies. My first reaction was to try to advance that filter, but there were so many levels of code block that could be found, and there was so much I needed to learn and do to create the number of filters that I abandoned the idea.&lt;/p>
&lt;p>Still, &lt;a href="#pandoc">Pandoc&lt;/a> is a very powerful tool, so I started digging to find out if any embedded filters could help me filter only &lt;code>CodeBlocks&lt;/code>. And apparently, there are &lt;a href="https://pandoc.org/lua-filters.html" target="_blank" rel="noopener noreferrer">Pandoc Lua Filters&lt;/a>. After some experiments, I came up with the following:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">lua&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="cl">&lt;span class="n">traverse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'topdown'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">function&lt;/span> &lt;span class="nf">CodeBlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">block&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">if&lt;/span> &lt;span class="n">block.classes&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">"sh"&lt;/span> &lt;span class="kr">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"#-----CodeBlock-----"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">io.stdout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">block.text&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">return&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">end&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This gives me a sequence of blocks that are marked as &lt;code>sh&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl"> pandoc -i podman.md --lua-filter ../../../_resources/bin/CodeBlock.lua -t html -o /dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#-----CodeBlock-----&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p ~/.config/pmm-server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &lt;&lt; &lt;span class="s2">"EOF"&lt;/span> > ~/.config/pmm-server/env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PMM_TAG&lt;/span>&lt;span class="o">=&lt;/span>2.31.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PMM_IMAGE&lt;/span>&lt;span class="o">=&lt;/span>docker.io/percona/pmm-server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PMM_PUBLIC_PORT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">8443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#-----CodeBlock-----&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl --user &lt;span class="nb">enable&lt;/span> --now pmm-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So that is easy to wrap up in a shell script and execute - locally or in a &lt;a href="https://github.com/percona/pmm-doc/blob/main/.github/workflows/podman-tests.yml#L35" target="_blank" rel="noopener noreferrer">GitHub Action&lt;/a>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> - name: Copy test template
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> run: cp _resources/bin/doc_test_template.sh ./docs_test_podman.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: Get CodeBlocks and push them to test template
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> run: pandoc -i docs/setting-up/server/podman.md --lua-filter _resources/bin/CodeBlock.lua -t html -o /dev/null >> docs_test_podman.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: Run podman tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> run: ./docs_test_podman.sh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Sometimes, you will find yourself in a situation where you need to execute something (env, infra, cleanup) that should not be shown in the documentation. For example, wait for the previous action before the next one:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;div hidden>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">sleep &lt;span class="m">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeout &lt;span class="m">60&lt;/span> podman &lt;span class="nb">wait&lt;/span> --condition&lt;span class="o">=&lt;/span>running pmm-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;/div>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I use &lt;code>html&lt;/code> to hide &lt;code>CodeBlocks&lt;/code> from the rendered document.&lt;/p>
&lt;p>You could solve documentation testing at least for some or most of the cases with these simple conventions:&lt;/p>
&lt;ul>
&lt;li>&lt;code>sh&lt;/code> language identifier for the fenced code blocks for examples&lt;/li>
&lt;li>&lt;code>&lt;div hidden>&lt;/code> for code blocks that should not be in the rendered documentation&lt;/li>
&lt;li>&lt;a href="https://linuxize.com/post/bash-heredoc/" target="_blank" rel="noopener noreferrer">Bash Heredoc&lt;/a> for files&lt;/li>
&lt;/ul>
&lt;p>This approach is easy to use locally to test documentation you just wrote and integrate into the CI pipeline.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;p>Here are some links. If you have some more suggestions - please open an issue or PR: &lt;a href="https://github.com/percona/community/" target="_blank" rel="noopener noreferrer">https://github.com/percona/community/&lt;/a> .&lt;/p>
&lt;h3 id="editors">Editors&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://orgmode.org/" target="_blank" rel="noopener noreferrer">https://orgmode.org/&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://howardism.org/Technical/Emacs/literate-devops.html" target="_blank" rel="noopener noreferrer">http://howardism.org/Technical/Emacs/literate-devops.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="github">GitHub&lt;/h3>
&lt;ul>
&lt;li>Library is used on GitHub.com to detect blob languages: &lt;a href="https://github.com/github/linguist/blob/master/lib/linguist/languages.yml" target="_blank" rel="noopener noreferrer">https://github.com/github/linguist/blob/master/lib/linguist/languages.yml&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks" target="_blank" rel="noopener noreferrer">https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="frameworks">Frameworks&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/Widdershin/markdown-doctest" target="_blank" rel="noopener noreferrer">https://github.com/Widdershin/markdown-doctest&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/nschloe/pytest-codeblocks" target="_blank" rel="noopener noreferrer">https://github.com/nschloe/pytest-codeblocks&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="pandoc">Pandoc&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://tomlankhorst.nl/testing-code-in-markdown-doc-md-github" target="_blank" rel="noopener noreferrer">https://tomlankhorst.nl/testing-code-in-markdown-doc-md-github&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pandoc.org/MANUAL.html" target="_blank" rel="noopener noreferrer">https://pandoc.org/MANUAL.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pandoc.org/filters.html" target="_blank" rel="noopener noreferrer">https://pandoc.org/filters.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pandoc.org/lua-filters.html" target="_blank" rel="noopener noreferrer">https://pandoc.org/lua-filters.html&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Denys Kondratenko</author><category>Documentation</category><category>Testing</category><category>Pandoc</category><media:thumbnail url="https://percona.community/blog/2023/02/doc-testing_hu_2b3938e6799f0c07.jpg"/><media:content url="https://percona.community/blog/2023/02/doc-testing_hu_b34e00ba5fd77d55.jpg" medium="image"/></item><item><title>Exploring Databases on Containers with Percona Server for MySQL</title><link>https://percona.community/blog/2023/02/23/exploring-databases-on-containers-with-mysql/</link><guid>https://percona.community/blog/2023/02/23/exploring-databases-on-containers-with-mysql/</guid><pubDate>Thu, 23 Feb 2023 00:00:00 UTC</pubDate><description>In this blog, we will explore databases on containers. We will use Docker as a container engine tool and Percona Server for MySQL as a database administration tool. Both are open source tools.</description><content:encoded>&lt;p>In this blog, we will explore databases on containers. We will use Docker as a container engine tool and &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a> as a database administration tool. Both are open source tools.&lt;/p>
&lt;p>&lt;strong>MySQL&lt;/strong> is a relational database management system that stores data on disk. Percona Server for &lt;strong>MySQL&lt;/strong> is a fork of MYSQL, providing much more advanced features. To run it correctly, we need to know volumes because we want to “persist” the data, the most important thing in databases.&lt;/p>
&lt;h2 id="running-a-single-percona-server-for-mysql-container">Running a single Percona Server for MySQL container&lt;/h2>
&lt;p>First, let’s create a container without volumes:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1652" height="832" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/1-volume_hu_1ec2ee39f9567d2f.webp 768w, https://percona.community/blog/2023/02/1-volume_hu_8a34ee8b67c459ce.webp 480w, https://percona.community/blog/2023/02/1-volume_hu_fcf0642fe9a2fddf.webp 1400w" src="https://percona.community/blog/2023/02/1-volume_hu_fcf0642fe9a2fddf.webp" alt="1-no-volume" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Figure 1: From Percona Server for MySQL image to a running container in Docker&lt;/p>
&lt;p>The following command will create a container called percona-server-1, where we can create databases and add some data.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d --name percona-server-1 -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>root percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Listing the image and the container:
&lt;figure>&lt;img width="1924" height="598" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/2-ls_hu_3c5054ea37300def.webp 768w, https://percona.community/blog/2023/02/2-ls_hu_10acb1b69bca89f9.webp 480w, https://percona.community/blog/2023/02/2-ls_hu_5163dae8ae8de05a.webp 1400w" src="https://percona.community/blog/2023/02/2-ls_hu_5163dae8ae8de05a.webp" alt="2-ls" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After the container is created:&lt;/p>
&lt;ul>
&lt;li>We have our base image, which is &lt;strong>percona/percona-server:8.0&lt;/strong>&lt;/li>
&lt;li>The base image in Docker is read-only. We can’t modify it. It allows you to spin up multiple containers from the same image with the same immutable base.&lt;/li>
&lt;li>We can add data to our image. This new layer is readable and writable.
If we create our database and populate it:&lt;/li>
&lt;/ul>
&lt;p>Accessing the detached container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-server-1 /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Connecting to the database&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mysql -uroot -proot&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a Database “cinema” and use it&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE DATABASE cinema&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">USE cinema&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create table movies in Database “cinema”&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE TABLE movies &lt;span class="o">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">book_id BIGINT PRIMARY KEY AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">title VARCHAR&lt;span class="o">(&lt;/span>100&lt;span class="o">)&lt;/span> UNIQUE NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">publisher VARCHAR&lt;span class="o">(&lt;/span>100&lt;span class="o">)&lt;/span> NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">labels JSON NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">)&lt;/span> &lt;span class="nv">ENGINE&lt;/span> &lt;span class="o">=&lt;/span> InnoDB&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Insert data into Database “cinema”&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">movies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">publisher&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">labels&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">‘&lt;/span>&lt;span class="n">Green&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">House&lt;/span>&lt;span class="err">’&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">‘&lt;/span>&lt;span class="n">Joe&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Monter&lt;/span>&lt;span class="err">’&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">’{“&lt;/span>&lt;span class="n">about&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{“&lt;/span>&lt;span class="n">gender&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">action&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">cool&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">notes&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">labeled&lt;/span>&lt;span class="err">”}}’&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Checking table&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">movies&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you delete this container, everything will be deleted, too, your databases and your data because containers are temporary.
&lt;figure>&lt;img width="1368" height="994" sizes="(max-width: 1368px) 100vw, 1368px" srcset="https://percona.community/blog/2023/02/3-image-no-volume_hu_a4d1e39734bef956.webp 768w, https://percona.community/blog/2023/02/3-image-no-volume_hu_1611005a7d265efe.webp 480w, https://percona.community/blog/2023/02/3-image-no-volume_hu_99c2f197e4adc4fc.webp 1368w" src="https://percona.community/blog/2023/02/3-image-no-volume_hu_99c2f197e4adc4fc.webp" alt="3-image-no-volume" loading="lazy" />&lt;/figure>
Figure 2: View of the layers that are generated when we create the container. Source: Severalnines AB&lt;/p>
&lt;h2 id="running-multiple-mysql-containers">Running Multiple MySQL Containers&lt;/h2>
&lt;p>Now let’s see how the layers of two different containers work together.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d --name percona-server-1 -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>root percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d --name percona-server-2 -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>root percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Multiple containers can share the same base image, which is read-only. Each container can have its data state for reading and writing (Which is built on the top of the base image), but this state will be lost if we don’t create persistent volumes that can ve saved after the container is shut down.
&lt;figure>&lt;img width="1592" height="1020" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/4-image-multiple-sql_hu_e13853e3094a06a6.webp 768w, https://percona.community/blog/2023/02/4-image-multiple-sql_hu_7e001d2827b7785c.webp 480w, https://percona.community/blog/2023/02/4-image-multiple-sql_hu_e04ddb5767be1079.webp 1400w" src="https://percona.community/blog/2023/02/4-image-multiple-sql_hu_e04ddb5767be1079.webp" alt="4-image-multiple-sql.png" loading="lazy" />&lt;/figure>
Figure 3: View the layers generated when we create two different containers. Source: Severalnines AB&lt;/p>
&lt;p>As we said before, “Volumes open the door for stateful applications to run efficiently in Docker.”&lt;/p>
&lt;h2 id="running-containers-with-persistent-volumes">Running containers with Persistent Volumes&lt;/h2>
&lt;p>Now we will create a container with a persistent volume in Docker.
&lt;figure>&lt;img width="1726" height="826" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/5-no-volume_hu_69ab61f0cc9d1b5b.webp 768w, https://percona.community/blog/2023/02/5-no-volume_hu_2f67f5c30daff8a9.webp 480w, https://percona.community/blog/2023/02/5-no-volume_hu_c284ea65198b4167.webp 1400w" src="https://percona.community/blog/2023/02/5-no-volume_hu_c284ea65198b4167.webp" alt="5-image-volume" loading="lazy" />&lt;/figure>
Figure 4: From Percona Server for MySQL image to a running container in Docker with volumes&lt;/p>
&lt;p>&lt;strong>percona-server&lt;/strong> is the base of the image. On top of that, we have all the changes we will make in the database. When we create the volume, we link a directory in the container with a directory on your local machine or in the machine where you want to persist the data.
When you delete the container, you can attach another container to this volume to have the same data on a different container.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d --name percona-server -e &lt;span class="nv">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>root -v local-datadir:/var/lib/mysql percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1648" height="1026" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/6-image-volume_hu_51f4eb93f5126cd8.webp 768w, https://percona.community/blog/2023/02/6-image-volume_hu_5e2cb4a3a1766d32.webp 480w, https://percona.community/blog/2023/02/6-image-volume_hu_82b64476fd5b2b5b.webp 1400w" src="https://percona.community/blog/2023/02/6-image-volume_hu_82b64476fd5b2b5b.webp" alt="6-image-volume" loading="lazy" />&lt;/figure>
Figure 4: View of the layers that are generated when we create the container with volume.&lt;/p>
&lt;h2 id="backing-up-and-restroring-databases">Backing up and restroring databases&lt;/h2>
&lt;p>There are two kinds of backups in databases, logical and physical backups.
We can use mysqldump to make logical backups and Percona XtraBackup, for physical backups. If we want to restore, we can use mysqldump and Percona XtraBackup, which offer much more advanced features.&lt;/p>
&lt;h2 id="back-up">Back up&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-server-backup mysqldump -uroot --password&lt;span class="o">=&lt;/span>root --single-transaction > /path/in/physical/host/dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="restore">Restore&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-server-restore mysql -u root --password&lt;span class="o">=&lt;/span>root &lt; /path/in/physical/host/dump.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now let’s share some tips to run databases on containers:&lt;/p>
&lt;ul>
&lt;li>Constantly monitor your database and host system&lt;/li>
&lt;li>Store data in a persistent volume outside the container&lt;/li>
&lt;li>Limit resource utilization, e.g., Memory, CPU&lt;/li>
&lt;li>Regularly back up the database and store the backup in a secure and separate location.&lt;/li>
&lt;li>Have a plan for database migrations and disaster recovery.&lt;/li>
&lt;/ul>
&lt;p>We explored how databases work on containers. Volumes are the important thing to persist the data.&lt;/p>
&lt;p>What is next? Watch this fantastic talk by Peter Zaitsev &lt;a href="https://www.youtube.com/watch?v=b_COgWA1lvk&amp;t=145s" target="_blank" rel="noopener noreferrer">Open Source Databases on Kubernetes&lt;/a>&lt;/p>
&lt;p>Thanks for reading this! You can install Percona Server for MySQL from our &lt;a href="https://hub.docker.com/r/percona/percona-server/tags??utm_source=percona-community&amp;utm_medium=blog&amp;utm_campaign=edith" target="_blank" rel="noopener noreferrer">Docker Repository&lt;/a> and if you have doubts write us in our &lt;a href="https://forums.percona.com/?utm_source=percona-community&amp;utm_medium=blog&amp;utm_campaign=edith" target="_blank" rel="noopener noreferrer">Percona community forum&lt;/a>.&lt;/p></content:encoded><author>Edith Puclla</author><category>Docker</category><category>MySQL</category><category>Volume</category><media:thumbnail url="https://percona.community/blog/2023/02/0-cover_hu_595fd28de0de994b.jpg"/><media:content url="https://percona.community/blog/2023/02/0-cover_hu_ac452a760a367ffb.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.35 preview release</title><link>https://percona.community/blog/2023/02/14/preview-release/</link><guid>https://percona.community/blog/2023/02/14/preview-release/</guid><pubDate>Tue, 14 Feb 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.35 preview release Hello folks! Percona Monitoring and Management (PMM) 2.35 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-235-preview-release">Percona Monitoring and Management 2.35 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.35 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>You can find the Release Notes &lt;a href="https://two-34-0-pr-977.onrender.com/release-notes/2.35.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker-installation">Percona Monitoring and Management server docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.35.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.35.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.35 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4898.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.35.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.35.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-09d19be2cfb10a60c&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us in &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">https://forums.percona.com/&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Releases</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Nurturing, Motivating and Recognizing Non-Code Contributions</title><link>https://percona.community/blog/2023/02/08/nurturing-motivating-and-recognizing-non-code-contributions/</link><guid>https://percona.community/blog/2023/02/08/nurturing-motivating-and-recognizing-non-code-contributions/</guid><pubDate>Wed, 08 Feb 2023 00:00:00 UTC</pubDate><description>When discussing contributions, we still see a lot of emphasis on the code contributions into project repositories. But the open source world is extensive and diverse, and everyone can find their place there. Your project will benefit from various experiences that non-coders can bring to the table. Isn’t that cool when you receive an issue with an interesting bug from the community, read about a user case in a blog or a review, or someone makes a video guide for your product? And more!</description><content:encoded>&lt;p>When discussing contributions, we still see a lot of emphasis on the code contributions into project repositories. But the open source world is extensive and diverse, and everyone can find their place there. Your project will benefit from various experiences that non-coders can bring to the table. Isn’t that cool when you receive an issue with an interesting bug from the community, read about a user case in a blog or a review, or someone makes a video guide for your product? And more!&lt;/p>
&lt;p>Non-code contributions may be of different types. You can learn more about them in my previous &lt;a href="https://percona.community/blog/2022/09/29/open-source-for-non-techs-find-your-way-to-contribute/" target="_blank" rel="noopener noreferrer">blog post&lt;/a>.&lt;/p>
&lt;p>In this article, we will look more into what could we do to bring in more non code contributions.&lt;/p>
&lt;h2 id="why-do-you-need-non-code-contributions">Why Do You Need Non-Code Contributions?&lt;/h2>
&lt;ul>
&lt;li>It gives you resources to fill the gaps. It is not a secret that developers often do not adore writing documentation. Or you may not have resources for testing or designing things, improving usability or localization for different markets. Coding is super important, but that is not always enough.&lt;/li>
&lt;li>It helps you to become more visible on socials and on software marketplaces where people come to choose a tool for their project and motivate people to try your software, for example, for their pet/student project.&lt;/li>
&lt;li>Furthermore, it motivates your developer team - seeing how people share T-shirts with your project name on Twitter, how many reviews they have, seeing that people use your software and create their own tutorials on YouTube, experimenting with it. That feeling that you do your work not for the work itself but for the people, for the people who not only use your project but spread the work about it, and give you feedback. Everyone will benefit from that.&lt;/li>
&lt;li>You do make the world a better place for everyone. Open doors to those who did not have opportunities to obtain systematic technical education due to different reasons. And it is not even necessary to explain that we all gain from a diversity of ideas and different experiences.&lt;/li>
&lt;/ul>
&lt;h2 id="what-could-we-do">What Could We Do?&lt;/h2>
&lt;p>Offer a small reward for the completion of particular tasks (that you find are important for your project). For example, we offered a T-shirt or a mug sent worldwide for leaving reviews on software marketplaces. People also tend to love Amazon 5 USD gift cards. You can launch campaigns with clear requirements, instructions and a due date to motivate people to act now and publish information about it on your blog or social media accounts. You can reward those who reported security issues. In Percona, we have Percona forum and reward active users who advise others. Not everyone has a forum, but if you have a Slack/Discord online chat for your community, you can do the same thing. Be creative and find what is important for you (and fits in your budget).&lt;/p>
&lt;p>Participate in different challenges like Hacktoberfest. In 2022, Hacktoberfest made an emphasis on bringing more non-code contributions and recognizing them too. Percona also participated in it, we had 20 contributions to our repositories and half (!) of them were non-code ones.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1308" height="632" sizes="(max-width: 1308px) 100vw, 1308px" srcset="https://percona.community/blog/2023/02/non_code_hu_bc789ef42aca7d1d.webp 768w, https://percona.community/blog/2023/02/non_code_hu_a86163b0ab0a588.webp 480w, https://percona.community/blog/2023/02/non_code_hu_2e68aa11d025dc18.webp 1308w" src="https://percona.community/blog/2023/02/non_code_hu_2e68aa11d025dc18.webp" alt="Non Code" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For your swag gifts, try to offer personalization if possible. For example, you can add individual messages on the t-shirt if you use print-on-demand providers. People share those opportunities around with their circle and pictures of provided swag on social media and spread the word about your open source project/brand. So they not only left a review or fixed several typos during Hacktoberfest, but also tweeted about you - their contribution became larger and you did not even had to ask them about that!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1358" height="644" sizes="(max-width: 1358px) 100vw, 1358px" srcset="https://percona.community/blog/2023/02/non_code1_hu_5cb2f779cbecc09d.webp 768w, https://percona.community/blog/2023/02/non_code1_hu_2c3ccf6d8079cae7.webp 480w, https://percona.community/blog/2023/02/non_code1_hu_3f4d6e29dab9cb43.webp 1358w" src="https://percona.community/blog/2023/02/non_code1_hu_3f4d6e29dab9cb43.webp" alt="Non Code" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Make reward visible and document it for the history. &lt;a href="https://www.percona.com/blog/hacktoberfest-results-percona-honors-the-contributors/" target="_blank" rel="noopener noreferrer">Publish blog posts/social media posts&lt;/a> to thank people who did a great job - those who reported issues or provided important suggestions included in the latest release, contributed during Hacktoberfest, etc.
We also publish all contributions (&lt;a href="https://percona.community/contribute/videos/" target="_blank" rel="noopener noreferrer">community videos&lt;/a>, &lt;a href="https://percona.community/contribute/articles/" target="_blank" rel="noopener noreferrer">blog posts&lt;/a> that mention Percona) on our website percona.community and social networks (tweet about them, tagging authors) and the community likes the recognition.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1514" height="670" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/non_code2_hu_8bf7421d10602a92.webp 768w, https://percona.community/blog/2023/02/non_code2_hu_b1439ef2e9e20604.webp 480w, https://percona.community/blog/2023/02/non_code2_hu_a5dd1f43433cc35f.webp 1400w" src="https://percona.community/blog/2023/02/non_code2_hu_a5dd1f43433cc35f.webp" alt="Non Code" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Make sure to have guides on how to contribute and/or at least an email to contact. Involve non-coders from your team in testing interfaces and writing guides on how to contribute. &lt;a href="https://percona.community/blog/2022/02/10/how-to-publish-blog-post/" target="_blank" rel="noopener noreferrer">Offer simple guides with clear screenshots and instructions&lt;/a> or even guides for different level of experience - for those who have code experience and for those who have a few. Offer video guides. Simplify the process as much as you can. Try to use simple IDEs or tools. Leave a contact email everywhere for any questions or doubts people may have.&lt;/p>
&lt;p>Have a &lt;a href="https://percona.community/contribute/opentopics/" target="_blank" rel="noopener noreferrer">list of things&lt;/a> you need help with and publish it: list of open topics for your blog, list of how-tos in your documentation which you lack, etc.&lt;/p>
&lt;p>Provide a space for contributions. We host &lt;a href="https://percona.community/podcasts/" target="_blank" rel="noopener noreferrer">podcasts&lt;/a> and &lt;a href="https://percona.community/events/" target="_blank" rel="noopener noreferrer">community streams&lt;/a> by inviting authors from the community and use Restream, Riverside and Podbean for easy streaming. Start a podcast about your product and around. You can discuss not only coding, but all things open source, trends, use cases, etc.&lt;/p>
&lt;p>If you have Community Advocate program or reward Contributor of the Year, make sure you track non-code contributions too. It is a simple thing, but might be overlooked. We also have dashboards, custom-written and Orbit.Love, which helps us find contributors on Jira and GitHub. Also, we track talks about Percona in Jira and so they go to dashboards for easy tracking.&lt;/p>
&lt;p>In your messaging, emphasize why it matters. Why non-code contribution matters. And
explain that it is a contribution INDEED. Recognize it as a contribution. Non-coders tend to underestimate what they do (like - oh, I did not do anything special, everyone can do that - send a tweet or correct a spelling mistake in docs). That’s a mistake. Not everyone actually can and not everyone will actually do, stop by and pay attention, spend time on that.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1678" height="850" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/non_code3_hu_b685e7f2abe458f9.webp 768w, https://percona.community/blog/2023/02/non_code3_hu_3b094d35c32e5400.webp 480w, https://percona.community/blog/2023/02/non_code3_hu_d2da862be1672885.webp 1400w" src="https://percona.community/blog/2023/02/non_code3_hu_d2da862be1672885.webp" alt="Non Code" loading="lazy" />&lt;/figure>&lt;/p></content:encoded><author>Aleksandra Abramova</author><category>Contributions</category><category>Community</category><category>PMM</category><media:thumbnail url="https://percona.community/blog/2023/02/Alex-Fosdem-Non-Code_hu_aa4e27446a375a2e.jpg"/><media:content url="https://percona.community/blog/2023/02/Alex-Fosdem-Non-Code_hu_ff1394970418e6d1.jpg" medium="image"/></item><item><title>Node metrics available inside of a container</title><link>https://percona.community/blog/2023/02/06/node-metrics-container/</link><guid>https://percona.community/blog/2023/02/06/node-metrics-container/</guid><pubDate>Mon, 06 Feb 2023 00:00:00 UTC</pubDate><description>Several people asked me this question: Could we get Node metrics inside of a container?</description><content:encoded>&lt;p>Several people asked me this question: Could we get Node metrics inside of a container?&lt;/p>
&lt;p>Usually, this comes from the fact that PMM or standalone people run &lt;code>node_exporter&lt;/code> inside a container. PMM does it as a sidecar along with many other exporters to monitor DBs, and &lt;code>node_exporter&lt;/code> comes out of the box as a default one.
So people could see accurate data on dashboards, like Memory and CPU, that &lt;code>node_exporter&lt;/code> reads inside the container.&lt;/p>
&lt;p>My first reaction to this - the data is inaccurate, and if you need Node metrics, you need to run &lt;code>node_exporter&lt;/code> on the Node so it has proper access to the host system (VM or HW).
By inaccurate, I mean - not all data is there, and this data could be accurate only sometimes in some environments.&lt;/p>
&lt;p>But once a pretty technical person asked me, I needed to respond to this person with some tech details. There was correct data from PMM client coming from the Kubernetes container about the host it was running.&lt;/p>
&lt;p>One of the things I came up with that was true - you wouldn’t see the host process inside a container, and thus you wouldn’t see who and how is consuming memory and CPU.&lt;/p>
&lt;p>I need help understanding why Memory information, CPU, and others are correct.&lt;/p>
&lt;p>So I performed a small investigation to refresh my memory and learn more about namespaces, cgroups, and containers.&lt;/p>
&lt;h2 id="node_exporter">node_exporter&lt;/h2>
&lt;p>&lt;a href="https://github.com/prometheus/node_exporter#docker" target="_blank" rel="noopener noreferrer">node_exporter documentation&lt;/a> says:&lt;/p>
&lt;blockquote>
&lt;p>The &lt;code>node_exporter&lt;/code> is designed to monitor the host system. It’s not recommended
to deploy it as a Docker container because it requires access to the host system.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>For situations where Docker deployment is needed, some extra flags must be used to allow
the &lt;code>node_exporter&lt;/code> access to the host namespaces.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Be aware that any non-root mount points you want to monitor will need to be bind-mounted
into the container.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>If you start container for host monitoring, specify &lt;code>path.rootfs&lt;/code> argument.
This argument must match path in bind-mount of host root. The node_exporter will use
&lt;code>path.rootfs&lt;/code> as prefix to access host filesystem.&lt;/p>&lt;/blockquote>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -d &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --net&lt;span class="o">=&lt;/span>&lt;span class="s2">"host"&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --pid&lt;span class="o">=&lt;/span>&lt;span class="s2">"host"&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -v &lt;span class="s2">"/:/host:ro,rslave"&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> quay.io/prometheus/node-exporter:latest &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --path.rootfs&lt;span class="o">=&lt;/span>/host&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>On some systems, the timex collector requires an additional Docker flag, –cap-add=SYS_TIME, in order to access the required syscalls.&lt;/p>&lt;/blockquote>
&lt;p>So right away, we can see that additional privileges are needed. More interestingly, not only access to the &lt;code>/proc&lt;/code> and &lt;code>/sys&lt;/code> is required, but to the whole &lt;code>/&lt;/code>. Also, some additional capabilities are needed.&lt;/p>
&lt;p>If we will look briefly at the &lt;code>node_exporter&lt;/code> code, we will indeed find different technics it uses to gather data:&lt;/p>
&lt;ul>
&lt;li>&lt;code>procfs&lt;/code> data&lt;/li>
&lt;li>&lt;code>sysfs&lt;/code> data&lt;/li>
&lt;li>D-Bus socket (systemd data)&lt;/li>
&lt;li>system calls (timex)&lt;/li>
&lt;li>and probably more (udev, device data and etc)&lt;/li>
&lt;/ul>
&lt;h2 id="container">Container&lt;/h2>
&lt;p>Containers and their ecosystem is quite a big topic that is described many times. Please check out “Demystifying Containers” by &lt;a href="https://www.suse.com/c/author/sgrunert/" target="_blank" rel="noopener noreferrer">Sascha Grunert&lt;/a> and “Building containers by hand” by &lt;a href="https://www.redhat.com/sysadmin/users/steve-ovens" target="_blank" rel="noopener noreferrer">Steve Ovens&lt;/a>. You can find them in &lt;a href="#links">Links&lt;/a> section.&lt;/p>
&lt;p>What is related to my investigation is isolation from the host, and that is mostly &lt;code>namespaces&lt;/code> and &lt;code>cgroups&lt;/code>.&lt;/p>
&lt;p>Other systems that are limiting access to the different files and calls inside a container:&lt;/p>
&lt;ol>
&lt;li>capabilities&lt;/li>
&lt;li>seccomp&lt;/li>
&lt;li>selinux/apparmor&lt;/li>
&lt;li>additional security options&lt;/li>
&lt;/ol>
&lt;h3 id="namespaces-and-cgroup">&lt;code>namespaces&lt;/code> and &lt;code>cgroup&lt;/code>&lt;/h3>
&lt;p>Let us focus only on &lt;code>procfs&lt;/code>, where a lot of needed monitoring information comes from. I aim to understand why we have some data in &lt;code>/proc&lt;/code> that corresponds to the host data and some that do not.&lt;/p>
&lt;p>First, &lt;code>/proc&lt;/code> is a &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/proc.html" target="_blank" rel="noopener noreferrer">special filesystem&lt;/a> that acts as an interface to internal data structures in the kernel. It can obtain information about the system and change certain kernel parameters at runtime (sysctl).&lt;/p>
&lt;p>It is also quite an old interface that was created before any &lt;code>namespaces&lt;/code> and &lt;code>cgroup&lt;/code>. Many different applications expect the data there, and thus it can’t be easily namespaced.&lt;/p>
&lt;p>Here is what was namespaced:&lt;/p>
&lt;ol>
&lt;li>net&lt;/li>
&lt;li>uts&lt;/li>
&lt;li>ipc&lt;/li>
&lt;li>pid&lt;/li>
&lt;li>user&lt;/li>
&lt;li>cgroups&lt;/li>
&lt;li>time&lt;/li>
&lt;/ol>
&lt;p>Same in code:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/namespaces.c#n15" target="_blank" rel="noopener noreferrer">https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/namespaces.c#n15&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://elixir.bootlin.com/linux/latest/source/include/linux/proc_ns.h#L27" target="_blank" rel="noopener noreferrer">https://elixir.bootlin.com/linux/latest/source/include/linux/proc_ns.h#L27&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>So it means that a lot of data under, for example, &lt;code>/proc/net&lt;/code> would be container specific. Same for other subsystems.&lt;/p>
&lt;p>But the biggest difference for monitoring when &lt;code>namespaces&lt;/code> and &lt;code>cgroup&lt;/code> are used is that container has the access only to its own &lt;code>PID&lt;/code> namespace. That means that even if we see all available memory or CPU, we can’t tell what processes from the host system could consume that. We could only see our namespace processes.&lt;/p>
&lt;p>It is tough to tell what exactly namespaced under &lt;code>/proc&lt;/code>. It looks like all the files (not dirs) under &lt;code>/proc&lt;/code> are directly from the host kernel.&lt;/p>
&lt;p>Thus we could see many files (they aren’t real files) from the host/root namespace and many that are specific to the container namespace.&lt;/p>
&lt;p>For example, here you see uts (hostname) and network namespaces differences between root and container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>root@0d514d31c0a3 opt&lt;span class="o">]&lt;/span>$ cat /proc/net/dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Inter-&lt;span class="p">|&lt;/span> Receive &lt;span class="p">|&lt;/span> Transmit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> face &lt;span class="p">|&lt;/span>bytes packets errs drop fifo frame compressed multicast&lt;span class="p">|&lt;/span>bytes packets errs drop fifo colls carrier compressed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> lo: &lt;span class="m">250045720&lt;/span> &lt;span class="m">467744&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">250045720&lt;/span> &lt;span class="m">467744&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tap0: &lt;span class="m">37944098&lt;/span> &lt;span class="m">3043&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">173264&lt;/span> &lt;span class="m">2426&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>dkondratenko@denlen ~&lt;span class="o">]&lt;/span>$ cat /proc/net/dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Inter-&lt;span class="p">|&lt;/span> Receive &lt;span class="p">|&lt;/span> Transmit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> face &lt;span class="p">|&lt;/span>bytes packets errs drop fifo frame compressed multicast&lt;span class="p">|&lt;/span>bytes packets errs drop fifo colls carrier compressed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> lo: &lt;span class="m">1674394721&lt;/span> &lt;span class="m">1671478&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">1674394721&lt;/span> &lt;span class="m">1671478&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">enp2s0f0: &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wwan0: &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wlp3s0: &lt;span class="m">3869584887&lt;/span> &lt;span class="m">6162652&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">88037&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">1639861087&lt;/span> &lt;span class="m">3732759&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cni-podman0: &lt;span class="m">3688&lt;/span> &lt;span class="m">53&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">53&lt;/span> &lt;span class="m">22094&lt;/span> &lt;span class="m">163&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see, network interfaces are different (&lt;code>net&lt;/code>) and hostnames (&lt;code>uts&lt;/code> namespace, &lt;code>0d514d31c0a3&lt;/code> in the container and &lt;code>denlen&lt;/code> in the host).&lt;/p>
&lt;h2 id="linux-capabilities-and-seccomp">Linux Capabilities and seccomp&lt;/h2>
&lt;p>&lt;a href="https://man7.org/linux/man-pages/man7/capabilities.7.html" target="_blank" rel="noopener noreferrer">Linux Capabilities&lt;/a> allow access for the unprivileged processes to perform some actions/call in the system.
In &lt;a href="#node_exporter">node_exporter&lt;/a> section, we have seen an example of the &lt;code>CAP_SYS_TIME&lt;/code> that is needed to gather some of the data.&lt;/p>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Seccomp" target="_blank" rel="noopener noreferrer">seccomp&lt;/a> is a computer security facility in the Linux kernel. seccomp allows a process to make a one-way transition into a “secure” state where it cannot make any system calls except exit(), sigreturn(), read(), and write() to already-open file descriptors.&lt;/p>
&lt;p>So both systems further restrict access to the data that might be needed to gather monitoring information.&lt;/p>
&lt;p>Docker, Podman have default seccomp filters:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/moby/moby/blob/master/profiles/seccomp/default.json" target="_blank" rel="noopener noreferrer">https://github.com/moby/moby/blob/master/profiles/seccomp/default.json&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/containers/common/blob/main/pkg/seccomp/seccomp.json" target="_blank" rel="noopener noreferrer">https://github.com/containers/common/blob/main/pkg/seccomp/seccomp.json&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="linux-security-modules">Linux Security Modules&lt;/h2>
&lt;p>Security-Enhanced Linux, &lt;a href="https://en.wikipedia.org/wiki/Security-Enhanced_Linux" target="_blank" rel="noopener noreferrer">SELinux&lt;/a> is a Linux kernel security module that provides a mechanism for supporting access control security policies, including mandatory access controls (MAC).&lt;/p>
&lt;p>&lt;a href="AppArmor">AppArmor&lt;/a> (“Application Armor”) is a Linux kernel security module that allows the system administrator to restrict programs’ capabilities with per-program profiles.&lt;/p>
&lt;p>Both of those could further restrict access inside the container. For example, here is the part of the &lt;a href="https://docs.docker.com/engine/security/apparmor/#nginx-example-profile" target="_blank" rel="noopener noreferrer">apparmor&lt;/a> profile:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # deny write to files not in /proc/&lt;number>/** or /proc/sys/**
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/sysrq-trigger rwklx,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/mem rwklx,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/kmem rwklx,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deny @{PROC}/kcore rwklx,&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So it is possible to restrict access even for those root &lt;code>/proc&lt;/code> files that provide memory and CPU information.&lt;/p>
&lt;h2 id="additional-security-options">Additional security options&lt;/h2>
&lt;p>Container runtimes and tools could further harden security.&lt;/p>
&lt;p>One example is masking mount points:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#security-opt-option" target="_blank" rel="noopener noreferrer">https://docs.podman.io/en/latest/markdown/podman-run.1.html#security-opt-option&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/containers/podman/blob/ab7f6095a17bd50477c30fc8c127a8604b5693a6/pkg/specgen/generate/config_linux.go#L91" target="_blank" rel="noopener noreferrer">https://github.com/containers/podman/blob/ab7f6095a17bd50477c30fc8c127a8604b5693a6/pkg/specgen/generate/config_linux.go#L91&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>root@0d514d31c0a3 opt&lt;span class="o">]&lt;/span>$ mount &lt;span class="p">|&lt;/span> grep proc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>rw,nosuid,nodev,noexec,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmpfs on /proc/acpi &lt;span class="nb">type&lt;/span> tmpfs &lt;span class="o">(&lt;/span>ro,relatime,context&lt;span class="o">=&lt;/span>&lt;span class="s2">"system_u:object_r:container_file_t:s0:c11,c680"&lt;/span>,size&lt;span class="o">=&lt;/span>0k,uid&lt;span class="o">=&lt;/span>1000,gid&lt;span class="o">=&lt;/span>1000,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">devtmpfs on /proc/kcore &lt;span class="nb">type&lt;/span> devtmpfs &lt;span class="o">(&lt;/span>rw,nosuid,seclabel,size&lt;span class="o">=&lt;/span>4096k,nr_inodes&lt;span class="o">=&lt;/span>1048576,mode&lt;span class="o">=&lt;/span>755,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">devtmpfs on /proc/keys &lt;span class="nb">type&lt;/span> devtmpfs &lt;span class="o">(&lt;/span>rw,nosuid,seclabel,size&lt;span class="o">=&lt;/span>4096k,nr_inodes&lt;span class="o">=&lt;/span>1048576,mode&lt;span class="o">=&lt;/span>755,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">devtmpfs on /proc/latency_stats &lt;span class="nb">type&lt;/span> devtmpfs &lt;span class="o">(&lt;/span>rw,nosuid,seclabel,size&lt;span class="o">=&lt;/span>4096k,nr_inodes&lt;span class="o">=&lt;/span>1048576,mode&lt;span class="o">=&lt;/span>755,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">devtmpfs on /proc/timer_list &lt;span class="nb">type&lt;/span> devtmpfs &lt;span class="o">(&lt;/span>rw,nosuid,seclabel,size&lt;span class="o">=&lt;/span>4096k,nr_inodes&lt;span class="o">=&lt;/span>1048576,mode&lt;span class="o">=&lt;/span>755,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmpfs on /proc/scsi &lt;span class="nb">type&lt;/span> tmpfs &lt;span class="o">(&lt;/span>ro,relatime,context&lt;span class="o">=&lt;/span>&lt;span class="s2">"system_u:object_r:container_file_t:s0:c11,c680"&lt;/span>,size&lt;span class="o">=&lt;/span>0k,uid&lt;span class="o">=&lt;/span>1000,gid&lt;span class="o">=&lt;/span>1000,inode64&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/asound &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/bus &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/fs &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/irq &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/sys &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc on /proc/sysrq-trigger &lt;span class="nb">type&lt;/span> proc &lt;span class="o">(&lt;/span>ro,relatime&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>The default masked paths are /proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/sched_debug, /proc/scsi, /proc/timer_list, /proc/timer_stats, /sys/firmware, and /sys/fs/selinux. The default paths that are read-only are /proc/asound, /proc/bus, /proc/fs, /proc/irq, /proc/sys, /proc/sysrq-trigger, /sys/fs/cgroup.&lt;/p>&lt;/blockquote>
&lt;p>And indeed, masking memory information is straightforward:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">podman run --detach --rm --replace&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> --name&lt;span class="o">=&lt;/span>pmm-server -p 4443:443/tcp --security-opt&lt;span class="o">=&lt;/span>&lt;span class="nv">mask&lt;/span>&lt;span class="o">=&lt;/span>/proc/meminfo:/proc/vmstat docker.io/percona/pmm-server:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>Kubernetes support most of the above technics as well. And different Kubernetes platforms have different security hardness.&lt;/p>
&lt;p>My knowledge at the beginning of this road needed to be deeper, but the conclusion stays the same - don’t assume that any data inside the container is related to the host.&lt;/p>
&lt;p>Looking at the technics, I didn’t know before and an overall trend of hardening security for the container, my conclusion would be - it is incorrect to assume that &lt;code>node_exporter&lt;/code> could read and provide any meaningful data about the host within the container.&lt;/p>
&lt;p>Container runtimes, tools, systems, and platforms provide the full capability to shut down, fake, and abstract any data or access that &lt;code>node_exporter&lt;/code> needs. And we couldn’t control those - assume you have incorrect data.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;h3 id="procfs">&lt;code>procfs&lt;/code>&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.kernel.org/doc/html/latest/filesystems/proc.html" target="_blank" rel="noopener noreferrer">https://www.kernel.org/doc/html/latest/filesystems/proc.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://fabiokung.com/2014/03/13/memory-inside-linux-containers/" target="_blank" rel="noopener noreferrer">https://fabiokung.com/2014/03/13/memory-inside-linux-containers/&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="cgroup">&lt;code>cgroup&lt;/code>&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html" target="_blank" rel="noopener noreferrer">https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.man7.org/linux/man-pages/man7/cgroups.7.html" target="_blank" rel="noopener noreferrer">https://www.man7.org/linux/man-pages/man7/cgroups.7.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="namespaces">&lt;code>namespaces&lt;/code>&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://man7.org/linux/man-pages/man7/namespaces.7.html" target="_blank" rel="noopener noreferrer">https://man7.org/linux/man-pages/man7/namespaces.7.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html" target="_blank" rel="noopener noreferrer">https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="containers">Containers&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.suse.com/c/author/sgrunert/" target="_blank" rel="noopener noreferrer">https://www.suse.com/c/author/sgrunert/&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.suse.com/c/demystifying-containers-part-i-kernel-space/" target="_blank" rel="noopener noreferrer">https://www.suse.com/c/demystifying-containers-part-i-kernel-space/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.suse.com/c/demystifying-containers-part-iv-container-security/" target="_blank" rel="noopener noreferrer">https://www.suse.com/c/demystifying-containers-part-iv-container-security/&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.redhat.com/sysadmin/users/steve-ovens" target="_blank" rel="noopener noreferrer">https://www.redhat.com/sysadmin/users/steve-ovens&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.redhat.com/sysadmin/7-linux-namespaces" target="_blank" rel="noopener noreferrer">https://www.redhat.com/sysadmin/7-linux-namespaces&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.redhat.com/sysadmin/building-container-namespaces" target="_blank" rel="noopener noreferrer">https://www.redhat.com/sysadmin/building-container-namespaces&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.redhat.com/sysadmin/mount-namespaces" target="_blank" rel="noopener noreferrer">https://www.redhat.com/sysadmin/mount-namespaces&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.redhat.com/sysadmin/pid-namespace" target="_blank" rel="noopener noreferrer">https://www.redhat.com/sysadmin/pid-namespace&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="linux-capabilities-and-seccomp-1">Linux Capabilities and Seccomp&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html" target="_blank" rel="noopener noreferrer">https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://man7.org/linux/man-pages/man7/capabilities.7.html" target="_blank" rel="noopener noreferrer">https://man7.org/linux/man-pages/man7/capabilities.7.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/engine/security/seccomp/" target="_blank" rel="noopener noreferrer">https://docs.docker.com/engine/security/seccomp/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/moby/moby/blob/master/profiles/seccomp/default.json" target="_blank" rel="noopener noreferrer">https://github.com/moby/moby/blob/master/profiles/seccomp/default.json&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/containers/common/blob/main/pkg/seccomp/seccomp.json" target="_blank" rel="noopener noreferrer">https://github.com/containers/common/blob/main/pkg/seccomp/seccomp.json&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="linux-security-modules-1">Linux Security Modules&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://docs.docker.com/engine/security/apparmor/" target="_blank" rel="noopener noreferrer">https://docs.docker.com/engine/security/apparmor/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/engine/security/apparmor/#nginx-example-profile" target="_blank" rel="noopener noreferrer">https://docs.docker.com/engine/security/apparmor/#nginx-example-profile&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="kubernetes-security">Kubernetes security&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/tasks/configure-pod-container/security-context/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/concepts/security/pod-security-standards/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/tutorials/security/apparmor/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/tutorials/security/apparmor/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/tutorials/security/seccomp/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/tutorials/security/seccomp/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-constraint-namespace/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-constraint-namespace/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Denys Kondratenko</author><category>Kubernetes</category><category>Monitoring</category><category>PMM</category><category>DBaaS</category><category>Containers</category><media:thumbnail url="https://percona.community/blog/2023/03/Container-Denys_hu_1ef8ffb063f5e8d.jpg"/><media:content url="https://percona.community/blog/2023/03/Container-Denys_hu_b01ad9e12d922c4.jpg" medium="image"/></item><item><title>Binding your application to the database in the Kubernetes cluster</title><link>https://percona.community/blog/2023/01/24/k8s-app-db-binding/</link><guid>https://percona.community/blog/2023/01/24/k8s-app-db-binding/</guid><pubDate>Tue, 24 Jan 2023 00:00:00 UTC</pubDate><description>dbaas-operator is Yet Another DBaaS Kubernetes Operator (need to suggest yadbko as a name) that tries to simplify and unify Database Cluster deployments by building a higher abstraction layer on top of Percona Kubernetes Operators.</description><content:encoded>&lt;p>&lt;a href="https://github.com/percona/dbaas-operator" target="_blank" rel="noopener noreferrer">dbaas-operator&lt;/a> is Yet Another DBaaS Kubernetes Operator (need to suggest yadbko as a name) that tries to simplify and unify Database Cluster deployments by building a higher abstraction layer on top of &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona Kubernetes Operators&lt;/a>.&lt;/p>
&lt;p>So it becomes much easier to deploy the DB cluster with &lt;code>dbaas-operator&lt;/code> and &lt;a href="https://docs.percona.com/percona-monitoring-and-management/get-started/dbaas.html" target="_blank" rel="noopener noreferrer">PMM DBaaS&lt;/a> on top of it.&lt;/p>
&lt;p>But another part of the picture is applications and their workloads to connect to the deployed DB Clusters.&lt;/p>
&lt;h2 id="services-and-applications">Services and Applications&lt;/h2>
&lt;p>On Kubernetes, application deployment could be done in many ways, either manually or as part of automatic deployments.&lt;/p>
&lt;p>PMM DBaaS offers both - UI to create DB Clusters and get credentials and API to automate those actions.&lt;/p>
&lt;p>&lt;code>dbaas-operator&lt;/code> adds Kubernetes native API to that batch as well.&lt;/p>
&lt;p>But both require additional automation to join the application and the database in one deployment and provide a service to the end user.&lt;/p>
&lt;p>And that operation is a challenging task, as every application could expect credentials in some specific format: secrets with hardcoded structures, environment variables with custom names, mount point secrets in particular locations, etc.&lt;/p>
&lt;p>Database services add their complexity to that picture by exposing their connections and secrets in a format that is convenient or makes sense for them.&lt;/p>
&lt;p>Usually, some Continues Delivery system or deployment package (helm, etc.) ensures all components’ correct deployment sequence and health. So many custom pipelines and packages exist to connect a specific application to a database service.&lt;/p>
&lt;p>But for simplicity and scalability, it would be nice to have some standard for connection or software that automates such a connection.&lt;/p>
&lt;h2 id="service-binding">Service Binding&lt;/h2>
&lt;p>Connecting services is a known pattern: Service Discovery (broker, registry, repository) for the &lt;a href="https://en.wikipedia.org/wiki/Service-oriented_architecture" target="_blank" rel="noopener noreferrer">Service-Oriented Architecture&lt;/a>.&lt;/p>
&lt;p>&lt;a href="https://servicebinding.io/" target="_blank" rel="noopener noreferrer">servicebinding.io&lt;/a> is another pattern to bind applications and workloads to the services (REST APIs, databases, event buses, etc.). This specification aims to create a Kubernetes-wide specification for communicating service secrets to workloads in a consistent way.&lt;/p>
&lt;p>&lt;a href="https://redhat-developer.github.io/service-binding-operator/userguide/intro.html" target="_blank" rel="noopener noreferrer">Service Binding Operator&lt;/a> glues services and Kubernetes workflows together. It does so for the services and applications that support ServiceBinding specifications and those that don’t.&lt;/p>
&lt;p>Out of the box Service Binding Operator supports &lt;a href="https://docs.percona.com/percona-operator-for-mysql/pxc/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL based on Percona XtraDB Cluster&lt;/a> (PXC), so we will deploy Database Cluster with &lt;code>dbaas-operator&lt;/code> and connect it to the simple Java application. We will use Spring PetClinic application that supports &lt;a href="https://github.com/spring-cloud/spring-cloud-bindings" target="_blank" rel="noopener noreferrer">Spring Cloud Bindings&lt;/a>.&lt;/p>
&lt;h2 id="create-an-environment">Create an environment&lt;/h2>
&lt;p>We need to have Kubernetes cluster, &lt;a href="https://olm.operatorframework.io/" target="_blank" rel="noopener noreferrer">Operator Lifecycle Manager&lt;/a> (OLM) to install operators, and all required operators installed. In this blog, I would use minikube and assume that &lt;code>operator-sdk&lt;/code> is installed on the system&lt;/p>
&lt;p>Here is a &lt;a href="https://github.com/denisok/k8s-connect-app-to-db/blob/main/assets/bin/service_binding.sh" target="_blank" rel="noopener noreferrer">link to the script&lt;/a> that:&lt;/p>
&lt;ul>
&lt;li>setups multi-node Kubernetes cluster&lt;/li>
&lt;li>installs OLM&lt;/li>
&lt;li>installs needed operators with the help of OLM&lt;/li>
&lt;/ul>
&lt;p>As a result we get cluster with all needed operators:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl get sub -A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAMESPACE NAME PACKAGE SOURCE CHANNEL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">default dbaas-operator dbaas-operator dbaas-catalog stable-v0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">default percona-server-mongodb-operator percona-server-mongodb-operator dbaas-catalog stable-v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">default percona-xtradb-cluster-operator percona-xtradb-cluster-operator dbaas-catalog stable-v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">operators my-service-binding-operator service-binding-operator operatorhubio-catalog stable&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="create-database-cluster">Create Database Cluster&lt;/h2>
&lt;p>We will use &lt;code>dbaas-operator&lt;/code> to demonstrate how easy it is to create DB Cluster with it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ cat &lt;span class="s">&lt;&lt;EOF | kubectl apply -f -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: dbaas.percona.com/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: DatabaseCluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: test-pxc-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> databaseType: pxc
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> databaseImage: percona/percona-xtradb-cluster:8.0.27-18.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> databaseConfig: |
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> [mysqld]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> wsrep_provider_options="debug=1;gcache.size=1G"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> wsrep_debug=1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> wsrep_trx_fragment_unit='bytes'
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> wsrep_trx_fragment_size=3670016
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> secretsName: pxc-sample-secrets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> clusterSize: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> loadBalancer:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> type: haproxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> exposeType: ClusterIP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> size: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> image: percona/percona-xtradb-cluster-operator:1.11.0-haproxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> dbInstance:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> cpu: "1"
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> memory: 1G
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> diskSize: 15G
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME SIZE READY STATUS ENDPOINT AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test-pxc-cluster &lt;span class="m">2&lt;/span> &lt;span class="m">2&lt;/span> ready test-pxc-cluster-haproxy.default 5m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="create-spring-petclinic-app-and-bind-it-to-the-database">Create Spring PetClinic app and bind it to the database&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl apply -f https://raw.githubusercontent.com/redhat-developer/service-binding-operator/master/samples/apps/spring-petclinic/petclinic-mysql-deployment.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deployment.apps/spring-petclinic created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/spring-petclinic created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spring-petclinic-f7f587c5c-rvq2v 0/1 CrashLoopBackOff &lt;span class="m">2&lt;/span> &lt;span class="o">(&lt;/span>17s ago&lt;span class="o">)&lt;/span> 67s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we didn’t create a binding yet, the application can’t connect to the database and thus fails.&lt;/p>
&lt;p>Let us bind application to the database and verify it is working:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ cat &lt;span class="s">&lt;&lt;EOF | kubectl apply -f -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">apiVersion: binding.operators.coreos.com/v1alpha1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">kind: ServiceBinding
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: spring-petclinic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> namespace: default
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> services:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - group: pxc.percona.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> version: v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> kind: PerconaXtraDBCluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: test-pxc-cluster
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> application:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> name: spring-petclinic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> group: apps
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> version: v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> resource: deployments
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get servicebindings
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY REASON AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spring-petclinic True ApplicationsBound 4m47s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get deployments
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY UP-TO-DATE AVAILABLE AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spring-petclinic 1/1 &lt;span class="m">1&lt;/span> &lt;span class="m">1&lt;/span> 17m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube service spring-petclinic --url
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">http://192.168.39.215:31181&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What we have done above:&lt;/p>
&lt;ol>
&lt;li>Created &lt;code>kind: ServiceBinding&lt;/code>, which takes PXC secrets and maps them to the application as mount points.&lt;/li>
&lt;li>As PetClinic supports ServiceBinding spec with Spring framework, it understands those mount points and connects to the database.&lt;/li>
&lt;/ol>
&lt;p>Here is what mount point by ServiceBinding specification that Spring Cloud Bindings library parsed and connected to the database:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> deployment/spring-petclinic -- ls -la /bindings/spring-petclinic/..2023_01_20_21_33_47.4121788695
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">56&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">2&lt;/span> root root &lt;span class="m">320&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxrwxrwt &lt;span class="m">3&lt;/span> root root &lt;span class="m">360&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">18&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 clustercheck
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">5&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">32&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 host
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">17&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 monitor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">17&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">18&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 password
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">4&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 port
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">7&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 provider
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">17&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 proxyadmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">18&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 replication
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">18&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">5&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 &lt;span class="nb">type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">4&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 username
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> root root &lt;span class="m">17&lt;/span> Jan &lt;span class="m">20&lt;/span> 21:33 xtrabackup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> deployment/spring-petclinic -- cat /bindings/spring-petclinic/..2023_01_20_21_33_47.4121788695/database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> deployment/spring-petclinic -- cat /bindings/spring-petclinic/..2023_01_20_21_33_47.4121788695/host
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">test-pxc-cluster-haproxy.default&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check the url that was exposed by minikube:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1543" height="626" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/02/petclinic_hu_54064e2eeb32eacd.webp 768w, https://percona.community/blog/2023/02/petclinic_hu_250f27fcd053b0ff.webp 480w, https://percona.community/blog/2023/02/petclinic_hu_a533d720ba686a6b.webp 1400w" src="https://percona.community/blog/2023/02/petclinic_hu_a533d720ba686a6b.webp" alt="Pet Clinic" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>There are many ways to deploy applications and services and connect them.&lt;/p>
&lt;p>I am trying to collect some of them under my &lt;a href="https://github.com/denisok/k8s-connect-app-to-db" target="_blank" rel="noopener noreferrer">personal repo&lt;/a> to understand the problem deeper. Please suggest other ways by commenting under this blog or in repo.&lt;/p>
&lt;p>ServiceBinding specification is a standardized way that scales easily and allows you to connect Kubernetes workloads to the database services.&lt;/p>
&lt;p>I will propose to &lt;code>dbaas-operator&lt;/code> to implement that specification so that it could expose different Database engines (mysql, mongo, pg) in a standard way.&lt;/p></content:encoded><author>Denys Kondratenko</author><category>Labs</category><category>Kubernetes</category><category>Operators</category><category>Databases</category><category>PMM</category><category>DBaaS</category><category>Minikube</category><media:thumbnail url="https://percona.community/blog/2023/02/petclinic_hu_1a8d4824c10dfb88.jpg"/><media:content url="https://percona.community/blog/2023/02/petclinic_hu_a952755f21de9430.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.34 preview release</title><link>https://percona.community/blog/2023/01/17/preview-release/</link><guid>https://percona.community/blog/2023/01/17/preview-release/</guid><pubDate>Tue, 17 Jan 2023 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.34 preview release Hello folks! Percona Monitoring and Management (PMM) 2.34 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-234-preview-release">Percona Monitoring and Management 2.34 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.34 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>You can find the Release Notes &lt;a href="https://two-34-0-pr-954.onrender.com/release-notes/2.34.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker-installation">Percona Monitoring and Management server docker installation&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.34.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.34.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.34 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4747.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.34.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.34.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-08c09a75c3dd22956&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us in &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">https://forums.percona.com/&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><category>Releases</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Setting Up PMM For Monitoring Your Databases on Windows</title><link>https://percona.community/blog/2023/01/16/setting-up-pmm-for-monitoring-your-databases-on-windows/</link><guid>https://percona.community/blog/2023/01/16/setting-up-pmm-for-monitoring-your-databases-on-windows/</guid><pubDate>Mon, 16 Jan 2023 00:00:00 UTC</pubDate><description>Before deploying Percona Monitoring and Management (PMM) in production, you might want to test it or set up a development instance locally. Since many developers and DBAs have Windows desktops, I wanted to demonstrate how to set up PMM on Windows for an easy test environment. In this post, I’ll walk you through setting up PMM with Docker and WSL.</description><content:encoded>&lt;p>Before deploying Percona Monitoring and Management (PMM) in production, you might want to test it or set up a development instance locally. Since many developers and DBAs have Windows desktops, I wanted to demonstrate how to set up PMM on Windows for an easy test environment. In this post, I’ll walk you through setting up PMM with Docker and WSL.&lt;/p>
&lt;p>If you’re a Linux user, check the blog post I wrote on &lt;a href="https://percona.community/blog/2022/08/05/setting-up-pmm-for-monitoring-mysql-on-a-local-environment/" target="_blank" rel="noopener noreferrer">Setting up PMM for monitoring MySQL in a local environment&lt;/a>. There you can find instructions for installing Percona Monitoring and Management (PMM) on Linux and how to set it up for monitoring a MySQL instance. Otherwise, continue reading to get PMM up and running on Windows.&lt;/p>
&lt;h2 id="getting-started-with-pmm-on-windows">Getting Started With PMM on Windows&lt;/h2>
&lt;p>If you’re a Windows user and want to try PMM, the recommended way for installing both the server and client would be to use the official Docker images and follow these guides:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Server&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/client/index.html#docker" target="_blank" rel="noopener noreferrer">Client&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Before running the commands in those guides, you should install Docker Desktop and Windows Subsystem for Linux (WSL). These instructions should work for users who are on current versions of Windows 10 and Windows 11. For installing WSL, follow the &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" target="_blank" rel="noopener noreferrer">instructions&lt;/a> on the Microsoft Learn website. Then, get the &lt;a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener noreferrer">Docker Desktop&lt;/a> installer. Now you’re ready to install and configure PMM.&lt;/p>
&lt;h2 id="pmm-server">PMM Server&lt;/h2>
&lt;p>As stated in the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>, you can store data from your PMM in:&lt;/p>
&lt;ul>
&lt;li>Docker volume (Preferred method)&lt;/li>
&lt;li>Data container&lt;/li>
&lt;li>Host directory&lt;/li>
&lt;/ul>
&lt;p>The preferred method is also recommended for Windows. Open PowerShell and execute the instructions in the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html#run-docker-with-volume" target="_blank" rel="noopener noreferrer">Run Docker with volume&lt;/a> section. I’ve reproduced the steps here to save you time:&lt;/p>
&lt;ol>
&lt;li>Get the Docker image:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker pull percona/pmm-server:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Create a volume:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker volume create pmm-data&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Run the image:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker run --detach --restart always \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--publish 443:443 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-v pmm-data:/srv \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--name pmm-server \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona/pmm-server:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Change the password for the default &lt;code>admin&lt;/code> user:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker &lt;span class="nb">exec&lt;/span> -t pmm-server change-admin-password &lt;new_password>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once PMM Server is installed, open the browser and visit https://localhost. You will see the PMM login screen.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1708" height="1312" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/01/pmm-login-screen_hu_5bd62d2064d5014b.webp 768w, https://percona.community/blog/2023/01/pmm-login-screen_hu_c583beeab71cd781.webp 480w, https://percona.community/blog/2023/01/pmm-login-screen_hu_96aa198e6fd82064.webp 1400w" src="https://percona.community/blog/2023/01/pmm-login-screen_hu_96aa198e6fd82064.webp" alt="PMM Login Screen" loading="lazy" />&lt;figcaption>PMM Login Screen&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>Now that the server is up and running, you need to get its IP address before connecting the client to the server. To get the IP address, you need the name or container ID. You can get it by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker ps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>docker ps&lt;/code> command will give you a list of the containers running on your system, as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fd988ad761aa percona/pmm-server:2 "/opt/entrypoint.sh" 2 months ago Up 11 minutes (healthy) 80/tcp, 0.0.0.0:443->443/tcp pmm-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Take note of the container ID or name of the PMM Server container. Then, execute this command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' your_container&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>your_container&lt;/code> with the container ID or name copied previously.&lt;/p>
&lt;h2 id="pmm-client">PMM Client&lt;/h2>
&lt;p>Go to the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/client/index.html#docker" target="_blank" rel="noopener noreferrer">Set Up PMM Client&lt;/a> section in the documentation and follow the first two steps. All of these commands are executed from PowerShell.&lt;/p>
&lt;p>In Step 3, you need to specify the IP address of the PMM Server by setting up the &lt;code>PMM_SERVER&lt;/code> environment variable:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nv">$env&lt;/span>:PMM_SERVER&lt;span class="o">=&lt;/span>’X.X.X.X:443’&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>X.X.X.X&lt;/code> with the server’s IP address. Then, initialize the container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker run \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--rm \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--name pmm-client \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_SERVER_ADDRESS=$env:PMM_SERVER \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_SERVER_USERNAME=admin \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_SERVER_PASSWORD=admin \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_SERVER_INSECURE_TLS=1 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_SETUP=1 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-e PMM_AGENT_CONFIG_FILE=config/pmm-agent.yaml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--volumes-from pmm-client-data \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">percona/pmm-client:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>PMM_AGENT_SERVER_PASSWORD&lt;/code> default value is &lt;code>admin&lt;/code>. Replace this value with the password assigned when the server was configured.&lt;/p>
&lt;h2 id="configure-your-database">Configure Your Database&lt;/h2>
&lt;p>Now that the client is connected to the server, you must configure PMM for monitoring your database. Follow the instructions below.&lt;/p>
&lt;ul>
&lt;li>MySQL
&lt;ul>
&lt;li>&lt;a href="#mysql-installation">Installation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#mysql-configuration">Configuration&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>PostgreSQL
&lt;ul>
&lt;li>&lt;a href="#postgresql-installation">Installation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#postgresql-configuration">Configuration&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>MongoDB
&lt;ul>
&lt;li>&lt;a href="#mongodb-installation">Installation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#mongodb-configuration">Configuration&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Once PMM is configured, the Home Dashboard will show the databases that are being monitored. For more information and advanced configuration, check the &lt;a href="https://docs.percona.com/percona-monitoring-and-management/index.html" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1720" height="1301" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/01/pmm-home-dashboard_hu_4f5cc74c9cce1a6d.webp 768w, https://percona.community/blog/2023/01/pmm-home-dashboard_hu_ce670f3c28ed3154.webp 480w, https://percona.community/blog/2023/01/pmm-home-dashboard_hu_9a04a760ca65b3ac.webp 1400w" src="https://percona.community/blog/2023/01/pmm-home-dashboard_hu_9a04a760ca65b3ac.webp" alt="PMM Home Dashboard" loading="lazy" />&lt;figcaption>PMM Home Dashboard&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;h3 id="mysql-installation">MySQL Installation&lt;/h3>
&lt;p>If you already have a MySQL instance running, skip the installation process and continue with the &lt;a href="#mysql-configuration">configuration&lt;/a>.&lt;/p>
&lt;p>On Windows, you can install Percona Server for MySQL on Ubuntu running under WSL, but it’s better to use the official &lt;a href="https://hub.docker.com/r/percona/percona-server/" target="_blank" rel="noopener noreferrer">Docker image&lt;/a>, as the MySQL server would be running on the same network as PMM.&lt;/p>
&lt;p>For installing MySQL using Docker, follow the instructions in the &lt;a href="https://docs.percona.com/percona-server/8.0/installation/docker.html" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a> documentation.&lt;/p>
&lt;p>You need to start the container with the latest version of Percona Server for MySQL 8.0:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ docker run -d \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --name ps \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -e MYSQL_ROOT_PASSWORD=root \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> percona/percona-server:8.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>ps&lt;/code> is the name of the container, and the default password for the &lt;code>root&lt;/code> user is &lt;code>root&lt;/code>. You can change these values according to your needs.&lt;/p>
&lt;h3 id="mysql-configuration">MySQL Configuration&lt;/h3>
&lt;p>Once Percona Server for MySQL is running, you need to get its IP address by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker ps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1fb4ddb35e48 percona/pmm-client:2 &lt;span class="s2">"/usr/local/percona/…"&lt;/span> &lt;span class="m">2&lt;/span> minutes ago Up &lt;span class="m">2&lt;/span> minutes pmm-client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Copy the container ID or name of the Percona Server for MySQL container. Then, execute this command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker inspect -f &lt;span class="s1">'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span> your_container&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>your_container&lt;/code> with the value you copied previously.&lt;/p>
&lt;p>The IP address of the PMM Client container is also needed.&lt;/p>
&lt;p>For configuring PMM for monitoring MySQL, we need to create a PMM user. First, log into MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker run -it --rm percona mysql -h MYSQL_SERVER -uroot -p&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>MYSQL_SERVER&lt;/code> is the IP address of the Percona Server for MySQL container&lt;/p>
&lt;p>Then, execute the following SQL statements&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pmm'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'localhost'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IDENTIFIED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pass'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MAX_USER_CONNECTIONS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESS&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SUPER&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REPLICATION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CLIENT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RELOAD&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKUP_ADMIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pmm'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'localhost'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>pass&lt;/code> with your desired password, and &lt;code>localhost&lt;/code> with the IP address of the PMM Client container.&lt;/p>
&lt;p>And finally, register the MySQL server for monitoring:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker &lt;span class="nb">exec&lt;/span> pmm-client pmm-admin add mysql --username&lt;span class="o">=&lt;/span>pmm --password&lt;span class="o">=&lt;/span>pass --host MYSQL_SERVER --query-source&lt;span class="o">=&lt;/span>perfschema&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>MYSQL_SERVER&lt;/code> is the IP address of the Percona Server for MySQL container. Replace this value with the IP address and replace &lt;code>pass&lt;/code> with the password of your &lt;code>pmm&lt;/code> user.&lt;/p>
&lt;h3 id="postgresql-installation">PostgreSQL Installation&lt;/h3>
&lt;p>If you already have a PostgreSQL instance running, skip the installation process and continue with the &lt;a href="#postgresql-configuration">configuration&lt;/a>.&lt;/p>
&lt;p>On Windows, you can install PostgreSQL using the Windows installer or install it on Ubuntu running under WSL, but it’s better to install it using the official image provided by the PostgreSQL project.&lt;/p>
&lt;p>You need to start the container with the latest version of PostgreSQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker run --name postgres -e &lt;span class="nv">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>password -d postgres&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>password&lt;/code> is the password for the default &lt;code>postgres&lt;/code> user. Replace this value according to your needs.&lt;/p>
&lt;h3 id="postgresql-configuration">PostgreSQL Configuration&lt;/h3>
&lt;p>Once PostgreSQL is running, you need to get its IP address by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker ps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">0460c671db12 postgres &lt;span class="s2">"docker-entrypoint.s…"&lt;/span> &lt;span class="m">6&lt;/span> days ago Up &lt;span class="m">48&lt;/span> seconds 5432/tcp postgres&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Copy the container ID or name of the PostgreSQL container. Then, execute this command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker inspect -f &lt;span class="s1">'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span> your_container&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>your_container&lt;/code> with the value you copied previously.&lt;/p>
&lt;p>The IP address of the PMM Client container is also needed.&lt;/p>
&lt;p>For configuring PMM for monitoring PostgreSQL, we need to create a PMM user. First, log into PostgreSQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker &lt;span class="nb">exec&lt;/span> -it postgres psql --user postgres&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, execute the following SQL statement:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pmm&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SUPERUSER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ENCRYPTED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PASSWORD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'&lt;password>'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>&lt;password>&lt;/code> with your desired password.&lt;/p>
&lt;p>And finally, register the PostgreSQL server for monitoring:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker &lt;span class="nb">exec&lt;/span> pmm-client pmm-admin add postgresql --username&lt;span class="o">=&lt;/span>pmm --password&lt;span class="o">=&lt;/span>pass --host POSTGRESQL_SERVER --query-source&lt;span class="o">=&lt;/span>perfschema&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>POSTGRESQL_SERVER&lt;/code> is the IP address of the PostgreSQL container. Replace this value with the IP address and replace &lt;code>pass&lt;/code> with the password of your &lt;code>pmm&lt;/code> user.&lt;/p>
&lt;h3 id="mongodb-installation">MongoDB Installation&lt;/h3>
&lt;p>If you already have a MongoDB instance running, skip the installation process and continue with the &lt;a href="#mongodb-configuration">configuration&lt;/a>.&lt;/p>
&lt;p>On Windows, you can install Percona Server for MongoDB on Ubuntu running under WSL, but it’s better to use the official &lt;a href="https://hub.docker.com/r/percona/percona-server/" target="_blank" rel="noopener noreferrer">Docker image&lt;/a>, as the MongoDB server would be running on the same network as PMM.&lt;/p>
&lt;p>For installing MongoDB using Docker, follow the instructions in the &lt;a href="https://docs.percona.com/percona-server-for-mongodb/6.0/install/docker.html" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a> documentation.&lt;/p>
&lt;p>You need to start the container with the latest version of Percona Server for MongoDB 6.0:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker run --name psmdb -d percona/percona-server-mongodb:6.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="mongodb-configuration">MongoDB Configuration&lt;/h3>
&lt;p>Once Percona Server for MongoDB is running, you need to get its IP address by running.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker ps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2c3d291535b3 percona/percona-server-mongodb:6.0 &lt;span class="s2">"/entrypoint.sh mong…"&lt;/span> &lt;span class="m">6&lt;/span> days ago Up &lt;span class="m">27&lt;/span> minutes 27017/tcp psmdb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Copy the container ID or name of the Percona Server for MongoDB container. Then, execute this command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker inspect -f &lt;span class="s1">'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span> your_container&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>your_container&lt;/code> with the value you copied previously.&lt;/p>
&lt;p>The IP address of the PMM Client container is also needed.&lt;/p>
&lt;p>For configuring PMM for monitoring MongoDB, we need to create a PMM user. First, connect to the &lt;code>admin&lt;/code> database in MongoDB using the MongoDB Shell (mongosh):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker run -it --link psmdb --rm percona/percona-server-mongodb:6.0 mongosh mongodb://MONGODB_SERVER:27017/admin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>MONGODB_SERVER&lt;/code> is the IP address of your MongoDB server.&lt;/p>
&lt;p>Then, create the user for PMM, executing the following instructions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">db.createRole({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "role":"explainRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "privileges":[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "resource":{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "db":"",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "collection":""
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "actions":[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "collStats",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "dbHash",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "dbStats",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "find",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "listIndexes",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "listCollections"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "roles":[]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">db.getSiblingDB("admin").createUser({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "user":"pmm",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "pwd":"&lt;password>",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "roles":[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "role":"explainRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "db":"admin"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "role":"clusterMonitor",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "db":"admin"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "role":"read",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "db":"local"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>&lt;password>&lt;/code> with the password you want to assign to the &lt;code>pmm&lt;/code> user.&lt;/p>
&lt;p>And finally, register the MongoDB server for monitoring:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ docker &lt;span class="nb">exec&lt;/span> pmm-client pmm-admin add mongodb --username&lt;span class="o">=&lt;/span>pmm --password&lt;span class="o">=&lt;/span>pass --host MONGODB_SERVER&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>MONGODB_SERVER&lt;/code> is the IP address of the MongoDB container. Replace this value with the IP address and replace &lt;code>pass&lt;/code> with the password of your &lt;code>pmm&lt;/code> user.&lt;/p></content:encoded><author>Mario García</author><category>PMM</category><category>MySQL</category><category>PostgreSQL</category><category>MongoDB</category><category>Monitoring</category><media:thumbnail url="https://percona.community/blog/2023/01/pmm-login-screen_hu_782bdd089cea0085.jpg"/><media:content url="https://percona.community/blog/2023/01/pmm-login-screen_hu_ca2504a4a3694f9d.jpg" medium="image"/></item><item><title>How To Generate Test Data for Your Database Project With Python</title><link>https://percona.community/blog/2023/01/09/how-to-generate-test-data-for-your-database-project-with-python/</link><guid>https://percona.community/blog/2023/01/09/how-to-generate-test-data-for-your-database-project-with-python/</guid><pubDate>Mon, 09 Jan 2023 00:00:00 UTC</pubDate><description>If you need test data for the database of your project, you can get a dataset from Kaggle or use a data generator. In the first case, if you need to process the data before inserting it into the database, you can use Pandas, a widely used Python library for data analysis. This library supports different formats, including CSV and JSON, and it also provides a method for inserting data into a SQL database.</description><content:encoded>&lt;p>If you need test data for the database of your project, you can get a dataset from &lt;a href="https://kaggle.com" target="_blank" rel="noopener noreferrer">Kaggle&lt;/a> or use a data generator. In the first case, if you need to process the data before inserting it into the database, you can use &lt;a href="https://pandas.pydata.org/" target="_blank" rel="noopener noreferrer">Pandas&lt;/a>, a widely used Python library for data analysis. This library supports different formats, including CSV and JSON, and it also provides a method for inserting data into a SQL database.&lt;/p>
&lt;p>If you choose a data generator instead, you can find one for &lt;a href="https://github.com/Percona-Lab/mysql_random_data_load" target="_blank" rel="noopener noreferrer">MySQL&lt;/a> in one of the repositories on our &lt;a href="https://github.com/Percona-Lab" target="_blank" rel="noopener noreferrer">Percona Lab&lt;/a> GitHub account. Are you using other database technologies? You can follow the guides I already published where I explain how to create your own data generator for &lt;a href="https://www.percona.com/blog/how-to-generate-test-data-for-mysql-with-python/" target="_blank" rel="noopener noreferrer">MySQL&lt;/a> (it could work for PostgreSQL) and &lt;a href="https://www.percona.com/blog/how-to-generate-test-data-for-mongodb-with-python/" target="_blank" rel="noopener noreferrer">MongoDB&lt;/a>.&lt;/p>
&lt;p>If you create you’re own data generator, this is the process you must follow:&lt;/p>
&lt;ul>
&lt;li>Generate fake data using Faker&lt;/li>
&lt;li>Store generated data in a Pandas DataFrame&lt;/li>
&lt;li>Establish a connection to your database&lt;/li>
&lt;li>Insert the content of the DataFrame into the database&lt;/li>
&lt;/ul>
&lt;h2 id="requirements">Requirements&lt;/h2>
&lt;h3 id="dependencies">Dependencies&lt;/h3>
&lt;p>Make sure all the dependencies are installed before creating the Python script that will generate the data for your project.&lt;/p>
&lt;p>You can create a &lt;code>requirements.txt&lt;/code> file with the following content:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pandas
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tqdm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">faker&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or if you’re using Anaconda, create an &lt;code>environment.yml&lt;/code> file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">dependencies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">python=3.10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">pandas&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">tqdm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">faker&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can change the Python version as this script has been proven to work with these versions of Python: 3.7, 3.8, 3.9, 3.10, and 3.11.&lt;/p>
&lt;p>Depending on the database technology you’re using, you must add the corresponding package to your &lt;code>requirements.txt&lt;/code> or &lt;code>environment.yml&lt;/code> file:&lt;/p>
&lt;ul>
&lt;li>MySQL → &lt;code>PyMySQL&lt;/code>&lt;/li>
&lt;li>PostgreSQL → &lt;code>psycopg2&lt;/code>&lt;/li>
&lt;li>MongoDB → &lt;code>pymongo&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Run the following command if you’re using &lt;code>pip&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install -r requirements.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or run the following statement to configure the project environment when using Anaconda:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">conda env create -f environment.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="database">Database&lt;/h3>
&lt;p>Now that you have the dependencies installed, you must create a database named &lt;code>company&lt;/code>, for MySQL or PostgreSQL.&lt;/p>
&lt;p>Log into MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mysql -u root -p&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replace &lt;code>root&lt;/code> with your username, if necessary, and replace &lt;code>localhost&lt;/code> with the IP address or URL of your MySQL server instance if needed.&lt;/p>
&lt;p>Or log into PostgreSQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo su postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ psql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>and create the &lt;code>company&lt;/code> database:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">database&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You don’t need to create the MongoDB database previously.&lt;/p>
&lt;h2 id="creating-a-pandas-dataframe">Creating a Pandas DataFrame&lt;/h2>
&lt;p>Before creating the script, it’s important to know that we need to implement multiprocessing for optimizing the execution time of the script.&lt;/p>
&lt;p>&lt;a href="https://docs.python.org/3/library/multiprocessing.html" target="_blank" rel="noopener noreferrer">Multiprocessing&lt;/a> is a way to take advantage of the CPU cores available in the computer where the script is running. In Python, single-CPU use is caused by the &lt;a href="https://realpython.com/python-gil/" target="_blank" rel="noopener noreferrer">global interpreter lock&lt;/a>, which allows only one thread to carry the Python interpreter at any given time. With multiprocessing, all the workload is divided into every CPU core available. For more information see &lt;a href="https://urban-institute.medium.com/using-multiprocessing-to-make-python-code-faster-23ea5ef996ba" target="_blank" rel="noopener noreferrer">this blog post&lt;/a>.&lt;/p>
&lt;p>Now, let’s start creating our own data generator. First, a &lt;code>modules&lt;/code> directory needs to be created, and inside the directory, we will create a module named &lt;code>dataframe.py&lt;/code>. This module will be imported later into our main script, and this is where we define the method that will generate the data.&lt;/p>
&lt;p>You need to import the required libraries and methods:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cpu_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pandas&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">pd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">tqdm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tqdm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">faker&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Faker&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>&lt;code>pandas&lt;/code>. Data generated with Faker will be stored in a Pandas DataFrame before being imported into the database.&lt;/li>
&lt;li>&lt;code>tqdm()&lt;/code>. This method is required for adding a progress bar to show the progress of the DataFrame creation.&lt;/li>
&lt;li>&lt;code>Faker()&lt;/code>. It’s the generator from the faker library.&lt;/li>
&lt;li>&lt;code>cpu_count()&lt;/code>. This is a method from the multiprocessing module that will return the number of cores available.&lt;/li>
&lt;/ul>
&lt;p>Then, a faker generator will be created and initialized, by calling the &lt;code>Faker()&lt;/code> method. This is required to generate data by accessing the properties in the Faker library.&lt;/p>
&lt;p>And we determine the number of cores of the CPU available, by calling the &lt;code>cpu_count()&lt;/code> method and assigning this value to the &lt;code>num_cores variable&lt;/code>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">fake&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Faker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">num_cores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cpu_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>num_cores&lt;/code> is a variable that stores the value returned after calling the &lt;code>cpu_count()&lt;/code> method. We use all the cores minus one to avoid freezing the computer.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_dataframe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arg&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">60000&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">num_cores&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DataFrame&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">tqdm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">desc&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'Creating DataFrame'&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'first_name'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first_name&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'last_name'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_name&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'job'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">job&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'company'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'address'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'city'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'country'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loc&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">'email'&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fake&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">data&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, we define the &lt;code>create_dataframe()&lt;/code> function, where:&lt;/p>
&lt;ul>
&lt;li>&lt;code>x&lt;/code> is the variable that will determine the number of iterations of the &lt;code>for&lt;/code> loop where the DataFrame is created.&lt;/li>
&lt;li>&lt;code>data&lt;/code> is an empty DataFrame that will later be fulfilled with data generated with Faker.&lt;/li>
&lt;li>Pandas &lt;a href="https://www.geeksforgeeks.org/python-pandas-dataframe-loc/" target="_blank" rel="noopener noreferrer">DataFrame.loc&lt;/a> attribute provides access to a group of rows and columns by their label(s). In each iteration, a row of data is added to the DataFrame and this attribute allows assigning values to each column.&lt;/li>
&lt;/ul>
&lt;p>The DataFrame that is created after calling this function will have the following columns:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Column Non-Null Count Dtype&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- ------ -------------- -----
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> first_name &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">1&lt;/span> last_name &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">2&lt;/span> job &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">3&lt;/span> company &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">4&lt;/span> address &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">5&lt;/span> country &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">6&lt;/span> city &lt;span class="m">60000&lt;/span> non-null object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">7&lt;/span> email &lt;span class="m">60000&lt;/span> non-null object&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Note&lt;/strong>: The script is generating 60 thousand records but it can be adapted to your project, you can modify this value in the &lt;code>x&lt;/code> variable.&lt;/p>
&lt;h2 id="connection-to-the-database">Connection to the Database&lt;/h2>
&lt;h3 id="mysql-and-postgresql">MySQL and PostgreSQL&lt;/h3>
&lt;p>Before inserting the data previously generated with Faker, we need to establish a connection to the database, and for doing this the SQLAlchemy library will be used.&lt;/p>
&lt;p>&lt;a href="https://www.sqlalchemy.org/" target="_blank" rel="noopener noreferrer">SQLAlchemy&lt;/a> is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_engine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.orm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">sessionmaker&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"mysql+pymysql://user:password@localhost/company"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sessionmaker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bind&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From SQLAlchemy, we import the &lt;code>create_engine()&lt;/code> and the &lt;code>sessionmaker()&lt;/code> methods. The first one is for connecting to the database, and the second one is for creating a session bond to the engine object.&lt;/p>
&lt;p>Don’t forget to replace &lt;code>user&lt;/code>, &lt;code>password&lt;/code>, and &lt;code>localhost&lt;/code> with your authentication details. Save this code in the &lt;code>modules&lt;/code> directory and name it as &lt;code>base.py&lt;/code>.&lt;/p>
&lt;p>For PostgreSQL, replace:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"mysql+pymysql://user:password@localhost/company"&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"postgresql+psycopg2://user:password@localhost:5432/company"&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="database-schema-definition">Database Schema Definition&lt;/h3>
&lt;p>For MySQL and PostgreSQL, the schema of the database can be defined through the &lt;a href="https://docs.sqlalchemy.org/en/14/core/schema.html" target="_blank" rel="noopener noreferrer">Schema Definition Language&lt;/a> provided by SQLAlchemy, but as we’re only creating one table and importing the DataFrame by calling Pandas to_sql() method, this is not necessary.&lt;/p>
&lt;p>When calling Pandas &lt;code>to_sql()&lt;/code> method, we define the schema as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="o">*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">schema&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"first_name"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"last_name"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"job"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"company"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"address"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"city"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"country"&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"email"&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then we pass the &lt;code>schema&lt;/code> variable as a parameter to this method.&lt;/p>
&lt;p>Save this code in the &lt;code>modules&lt;/code> directory with the name &lt;code>schema.py&lt;/code>.&lt;/p>
&lt;h3 id="mongodb">MongoDB&lt;/h3>
&lt;p>Before inserting the data previously generated with Faker, we need to establish a connection to the database, and for doing this the &lt;a href="https://pypi.org/project/pymongo/" target="_blank" rel="noopener noreferrer">PyMongo&lt;/a> library will be used.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pymongo&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MongoClient&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">uri&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">"mongodb://user:password@localhost:27017/"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MongoClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uri&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From PyMongo, we import the &lt;code>MongoClient()&lt;/code> method.&lt;/p>
&lt;p>Don’t forget to replace &lt;code>user&lt;/code>, &lt;code>password&lt;/code>, &lt;code>localhost&lt;/code>, and &lt;code>port&lt;/code> (27017) with your authentication details. Save this code in the modules directory and name it &lt;code>base.py&lt;/code>.&lt;/p>
&lt;h2 id="generating-your-data">Generating Your Data&lt;/h2>
&lt;h3 id="mysql-and-postgresql-1">MySQL and PostgreSQL&lt;/h3>
&lt;p>All the required modules are now ready to be imported into the main script, now it’s time to create the &lt;code>sql.py&lt;/code> script. First, import the required libraries:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Pool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cpu_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pandas&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">pd&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From multiprocessing, &lt;code>Pool()&lt;/code> and &lt;code>cpu_count()&lt;/code> are required. The &lt;a href="https://superfastpython.com/multiprocessing-pool-python/#:~:text=The%20Python%20Multiprocessing%20Pool%20class,Processes%20and%20Threads%20in%20Python." target="_blank" rel="noopener noreferrer">Python Multiprocessing Pool&lt;/a> class allows you to create and manage process pools in Python.&lt;/p>
&lt;p>Then, import the modules previously created:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">modules.dataframe&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_dataframe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">modules.schema&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">schema&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">modules.base&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">engine&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we create the multiprocessing pool, configured to use all available CPU cores minus one. Each core will call the &lt;code>create_dataframe()&lt;/code> function and create a DataFrame with 4 thousand records. After each call to the function has finished, all the DataFrames created will be concatenated into a single one.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">"__main__"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">num_cores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cpu_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">Pool&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">pool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">concat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pool&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">create_dataframe&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_cores&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_sql&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">'employees'&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">con&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">if_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">'append'&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And finally, we will insert the DataFrame into the MySQL database by calling the &lt;code>to_sql()&lt;/code> method. All the data will be stored in a table named employees.&lt;/p>
&lt;p>The table &lt;code>employees&lt;/code> is created without a primary key, so we execute the following SQL statement to add an &lt;code>id&lt;/code> column that is set to be the primary key of the table.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">conn&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">conn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"ALTER TABLE employees ADD id INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;"&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For PostgreSQL, replace this line:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">conn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"ALTER TABLE employees ADD id INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;"&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">conn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">"ALTER TABLE employees ADD COLUMN id SERIAL PRIMARY KEY;"&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="mongodb-1">MongoDB&lt;/h3>
&lt;p>All the required modules are now ready to be imported into the main script, now it’s time to create the &lt;code>mongodb.py&lt;/code> script. First, import the required libraries:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Pool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cpu_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pandas&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">pd&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From multiprocessing, &lt;code>Pool()&lt;/code> and &lt;code>cpu_count()&lt;/code> are required. The &lt;a href="https://superfastpython.com/multiprocessing-pool-python/#:~:text=The%20Python%20Multiprocessing%20Pool%20class,Processes%20and%20Threads%20in%20Python." target="_blank" rel="noopener noreferrer">Python Multiprocessing Pool&lt;/a> class allows you to create and manage process pools in Python.&lt;/p>
&lt;p>Then, import the modules previously created:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">modules.dataframe&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_dataframe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">modules.base&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">client&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we create the multiprocessing pool, configured to use all available CPU cores minus one. Each core will call the &lt;code>create_dataframe()&lt;/code> function and create a DataFrame with 4 thousand records. After each call to the function has finished, all the DataFrames created will be concatenated into a single one.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">"__main__"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">num_cores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cpu_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">Pool&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">pool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">concat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pool&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">create_dataframe&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_cores&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data_dict&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'records'&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"company"&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"employees"&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert_many&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_dict&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After logging into the MongoDB server, we specify the database and the collection where the data will be stored.&lt;/p>
&lt;p>And finally, we will insert the DataFrame into MongoDB by calling the &lt;code>insert_many()&lt;/code> method. All the data will be stored in a collection named &lt;code>employees&lt;/code>.&lt;/p>
&lt;h2 id="running-the-script">Running the script&lt;/h2>
&lt;p>Run the following statement to populate the table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ python sql.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ python mongodb.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1330" height="361" sizes="(max-width: 1330px) 100vw, 1330px" srcset="https://percona.community/blog/2023/01/multiprocessing_hu_b1ca3e3f47acc527.webp 768w, https://percona.community/blog/2023/01/multiprocessing_hu_bec1e50263c215e1.webp 480w, https://percona.community/blog/2023/01/multiprocessing_hu_af9a6864b2a6098d.webp 1330w" src="https://percona.community/blog/2023/01/multiprocessing_hu_af9a6864b2a6098d.webp" alt="Multiprocessing" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Execution time depends on the CPU cores available on your machine. I’m running this script on an Intel i7 1260P that has 16 cores, but using 15.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1502" height="380" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2023/01/cpu-utilization_hu_2e177386afca53d8.webp 768w, https://percona.community/blog/2023/01/cpu-utilization_hu_beca73aedb976720.webp 480w, https://percona.community/blog/2023/01/cpu-utilization_hu_a49ed3e766b43adc.webp 1400w" src="https://percona.community/blog/2023/01/cpu-utilization_hu_a49ed3e766b43adc.webp" alt="CPU Utilization" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="query-your-data">Query Your Data&lt;/h2>
&lt;p>Once the script finishes, you can check the data in the database.&lt;/p>
&lt;h3 id="mysql-and-postgresql-2">MySQL and PostgreSQL&lt;/h3>
&lt;p>Connect to the &lt;code>company&lt;/code> database.&lt;/p>
&lt;p>MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">company&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>PostgreSQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="err">\&lt;/span>&lt;span class="k">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">company&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, get the number of records.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">employees&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>count()&lt;/code> function returns the number of records in the &lt;code>employees&lt;/code> table.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> count&lt;span class="o">(&lt;/span>*&lt;span class="o">)&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="m">60000&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">1&lt;/span> row in &lt;span class="nb">set&lt;/span> &lt;span class="o">(&lt;/span>0.22 sec&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="mongodb-2">MongoDB&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">use company;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">db.employees.count()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>count()&lt;/code> function returns the number of records in the &lt;code>employees&lt;/code> table.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-32">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">60000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Or you can display the records in the &lt;code>employees&lt;/code> table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-33">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">db.employees.find().pretty()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The code shown in this blog post can be found on my GitHub account in the &lt;a href="https://github.com/mattdark/data-generator" target="_blank" rel="noopener noreferrer">data-generator&lt;/a> repository.&lt;/p></content:encoded><author>Mario García</author><category>Python</category><category>Dev</category><category>MySQL</category><category>PostgreSQL</category><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2023/01/testing_data_hu_ac911562155ce85c.jpg"/><media:content url="https://percona.community/blog/2023/01/testing_data_hu_9b8bc012b9fbed9f.jpg" medium="image"/></item><item><title>Automating Percona's XtraBackup</title><link>https://percona.community/blog/2023/01/04/automating-perconas-xtrabackup/</link><guid>https://percona.community/blog/2023/01/04/automating-perconas-xtrabackup/</guid><pubDate>Wed, 04 Jan 2023 00:00:00 UTC</pubDate><description>Percona’s XtraBackup is a beautiful tool that allows for the backup and restoration of MySQL databases.</description><content:encoded>&lt;p>&lt;a href="https://www.percona.com/software/mysql-database/percona-xtrabackup" target="_blank" rel="noopener noreferrer">Percona’s XtraBackup&lt;/a> is a beautiful tool that allows for the backup and restoration of MySQL databases.&lt;/p>
&lt;p>From the documentation:&lt;/p>
&lt;blockquote>
&lt;p>The Percona XtraBackup tools provide a method of performing a hot backup of your MySQL data while the system is running. Percona XtraBackup is a free, online, open source, complete database backups solution for all versions of Percona Server for MySQL and MySQL®. Percona XtraBackup performs online non-blocking, tightly compressed, highly secure full backups on transactional systems so that applications remain fully available during planned maintenance windows.&lt;/p>&lt;/blockquote>
&lt;p>It is great but it quickly becomes difficult to wield when using it multiple times per day across multiple environments. &lt;a href="https://github.com/phildoesdev/xtrabackupautomator" target="_blank" rel="noopener noreferrer">XtraBackup Automator&lt;/a> attempts to make this easier by providing the ability to:&lt;/p>
&lt;ul>
&lt;li>Schedule when we should create backups
&lt;ul>
&lt;li>Times of day, when to make a base backup vs incremental&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Archive old backups
&lt;ul>
&lt;li>Decide what to do with the base backup and its increments when we are ready to create a new base&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Maintain x days of archives
&lt;ul>
&lt;li>Define how many archived backup groups should we keep before removing them from the file system&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://github.com/phildoesdev/xtrabackupautomator" target="_blank" rel="noopener noreferrer">XtraBackup Automator&lt;/a> automates away the management of MySQL backups.&lt;/p>
&lt;h2 id="considerations-before-installing">Considerations Before Installing&lt;/h2>
&lt;p>I strongly recommend testing this on some sort of preproduction environment first. The thing I’ve seen most likely to cause trouble is the archival process. By default, this tool uses the gztar ’tarball’ as its compression method, which can be resource intensive if you are working on a large database backup. For instance, one of our servers (a Google Cloud Platform virtual machine with 8 vCPU, 32gb RAM, 1000GB SSD persistent disk, running Debian 10) with a ~140GB base backup currently jumps in CPU usage by ~13% for 4 hours, with a handful of 5%-15% jumps in RAM usage, while creating this archive. Another downside of this compression method is that it can take 10-20 minutes to unzip, depending on settings. The benefit of the tarball is that we are able to take these large backups from 140GB to &lt; 10GB and this is worth all that other trouble for us as we want to have two weeks of daily backups. If these down sides are not acceptable, I recommend playing with the archive type as described in the config. I have not personally tested any other methods.&lt;/p>
&lt;p>I am assuming that you have administrative access to the server this will run on as installing systemd services and timers requires root access. I see no reason why Cron Jobs could not be used to run this program, but I have never tested that and all documentation references systemd and its tools.&lt;/p>
&lt;h2 id="info--requirements">Info &amp; Requirements&lt;/h2>
&lt;h4 id="developed-on">Developed On&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>OS:&lt;/strong>
&lt;ul>
&lt;li>Debian GNU/Linux 10 (buster)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Python Version:&lt;/strong>
&lt;ul>
&lt;li>Python 3.10.4&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Python Packages&lt;/strong>
&lt;ul>
&lt;li>&lt;strong>Name:&lt;/strong> &lt;a href="https://pexpect.readthedocs.io/en/stable/" target="_blank" rel="noopener noreferrer">pexpect&lt;/a>, &lt;strong>Version:&lt;/strong> 4.8.0&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Percona XtraBackup Version:&lt;/strong>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/software/mysql-database/percona-xtrabackup" target="_blank" rel="noopener noreferrer">XtraBackup&lt;/a> version 8.0.28-21 based on MySQL server 8.0.28 Linux (x86_64)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>MySQL&lt;/strong>
&lt;ul>
&lt;li>MySql Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="required-python-libraries">Required Python Libraries&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://pexpect.readthedocs.io/en/stable/" target="_blank" rel="noopener noreferrer">pexpect&lt;/a>&lt;/li>
&lt;/ul>
&lt;h4 id="required-files">Required Files&lt;/h4>
&lt;ul>
&lt;li>&lt;a href="https://github.com/phildoesdev/xtrabackupautomator/blob/main/src/xtrabackupautomator.py" target="_blank" rel="noopener noreferrer">xtrabackupautomator.py&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/phildoesdev/xtrabackupautomator/blob/main/xtrabackupautomator.service" target="_blank" rel="noopener noreferrer">xtrabackupautomator.service&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/phildoesdev/xtrabackupautomator/blob/main/xtrabackupautomator.timer" target="_blank" rel="noopener noreferrer">xtrabackupautomator.timer&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="installing">Installing&lt;/h2>
&lt;p>Below is a general explanation of how to install and start running this program. I would suggest running the program manually via command line a couple times, in a preproduction environment, to verify things are working as you expect.&lt;/p>
&lt;p>&lt;em>&lt;strong>Download The Files&lt;/strong>&lt;/em>&lt;/p>
&lt;p>Download the &lt;a href="#required-files">Required Files&lt;/a> from &lt;a href="https://github.com/phildoesdev/xtrabackupautomator" target="_blank" rel="noopener noreferrer">https://github.com/phildoesdev/xtrabackupautomator&lt;/a>&lt;/p>
&lt;p>&lt;em>&lt;strong>Review Your Config Settings&lt;/strong>&lt;/em>&lt;/p>
&lt;p>Review the &lt;a href="#configuration">Configuration&lt;/a> section of this readme and alter these settings to your liking.&lt;br>
Any altered folder paths may affect the create folder instructions below. At minimum you must include database login information, but alter as necessary. I suggest reading through all the config options to see what might be interesting to tweak.&lt;/p>
&lt;p>&lt;em>&lt;strong>Edit your systemd service and timer&lt;/strong>&lt;/em>&lt;/p>
&lt;p>If you change the location that the script should run from you must alter the file path in the xtrabackupautomator.service file. I will not explain much else here as there is a lot that might go into these settings. I have given some default settings that hopefully make sense.&lt;/p>
&lt;p>I have also included several links that describe what is possible in the &lt;a href="#sources--links">Sources &amp; Links&lt;/a> section. If there are specific questions in the future I will address them here.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="865" height="495" sizes="(max-width: 865px) 100vw, 865px" srcset="https://percona.community/blog/2023/01/automate_xtrabackup_service_timer_example_hu_32c9540c618e0666.webp 768w, https://percona.community/blog/2023/01/automate_xtrabackup_service_timer_example_hu_5fce243822c513e3.webp 480w, https://percona.community/blog/2023/01/automate_xtrabackup_service_timer_example_hu_1e82d13c8b5f085d.webp 865w" src="https://percona.community/blog/2023/01/automate_xtrabackup_service_timer_example_hu_1e82d13c8b5f085d.webp" alt="ServiceTimerExample" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>&lt;strong>Install the required dependencies&lt;/strong>&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ python3 -m pip install pexpect&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Create the directory for our code to live in&lt;/strong>&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo mkdir /lib/xtrabackupautomator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod &lt;span class="m">700&lt;/span> /lib/xtrabackupautomator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Create the directories for our backups to save to&lt;/strong>&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo mkdir -p /data/backups/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mkdir -p /data/backups/archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mkdir -p /data/backups/archive/archive_restore
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod &lt;span class="m">760&lt;/span> /data/backups/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod &lt;span class="m">700&lt;/span> /data/backups/archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod &lt;span class="m">700&lt;/span> /data/backups/archive/archive_restore
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chown -R root:root /data/backups/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Move your downloaded files&lt;/strong>&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo mv xtrabackupautomator.py /lib/xtrabackupautomator/.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mv xtrabackupautomator.service /etc/systemd/system/.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mv xtrabackupautomator.timer /etc/systemd/system/.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Enable your service and timer&lt;/strong>&lt;/em>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo systemctl daemon-reload
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl &lt;span class="nb">enable&lt;/span> xtrabackupautomator.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl start xtrabackupautomator.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl status xtrabackupautomator.timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Congrats, you are now installed!&lt;/strong>&lt;/em>&lt;/p>
&lt;p>You have now installed XtraBackup Automator, it will begin running automatically according to your xtrabackupautomator.timer file. If you wish to run it manually, you can run the python file, or use my preferred method, the &lt;code>systemd start&lt;/code> command to start it and &lt;code>journalctl&lt;/code> to view its output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ systemctl start xtrabackupautomator.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ journalctl -f -n &lt;span class="m">100&lt;/span> -u xtrabackupautomator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>&lt;strong>Unzipping and Restoring your Backup&lt;/strong>&lt;/em>&lt;/p>
&lt;p>I have included a link in the &lt;a href="#sources--links">Sources &amp; Links&lt;/a> section on &lt;a href="https://linuxize.com/post/how-to-extract-unzip-tar-gz-file/" target="_blank" rel="noopener noreferrer">unzipping tar gz files&lt;/a>.&lt;/p>
&lt;p>I strongly suggest reading the &lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/backup_scenarios/incremental_backup.html" target="_blank" rel="noopener noreferrer">official Percona documentation&lt;/a> on restoring backups.&lt;/p>
&lt;p>For a point of a reference, I will describe my generic unzip and restore process. I am using the directory &lt;code>/data/backups/archive/archive_restore/&lt;/code> as a place to unzip and restore from.&lt;/p>
&lt;p>Executing any of the below commands can obviously be very dangerous as we must stop mysql, wipe the current data, and restore with our prepared backup. &lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/backup_scenarios/incremental_backup.html" target="_blank" rel="noopener noreferrer">Read the documentation&lt;/a> and come up with your own plan! The code below is only meant as a reference and may change greatly with time and environments. Always test your plans in a preprod environment!!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1016" height="404" sizes="(max-width: 1016px) 100vw, 1016px" srcset="https://percona.community/blog/2023/01/automate_xtrabackup_archive_pic_hu_3aabe00af0261d34.webp 768w, https://percona.community/blog/2023/01/automate_xtrabackup_archive_pic_hu_b6211389373a2d4b.webp 480w, https://percona.community/blog/2023/01/automate_xtrabackup_archive_pic_hu_37eee078f16b2639.webp 1016w" src="https://percona.community/blog/2023/01/automate_xtrabackup_archive_pic_hu_37eee078f16b2639.webp" alt="Archives" loading="lazy" />&lt;/figure>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Always verify our version of Percona's XtraBackup and MySQL match before performing a backup... these differences can make restores fail or behave oddly.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mysql --version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo xtrabackup --version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Clean our restore folder, just to be safe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rm -r /data/backups/archive/archive_restore/*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Unzip our archived backup to an empty folder. Always verify we have enough disk space to unzip before unzipping&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo tar -xvf database_backup_12_23_2022__06_25_10.tar.gz -C /data/backups/archive/archive_restore/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Sanity check, verify we are looking at the backup we think we are (This command checks the base folder, check the latest incremental folder we may have)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> /data/backups/archive/archive_restore/data/backups/mysql/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo cat base/xtrabackup_info &lt;span class="p">|&lt;/span> grep &lt;span class="s1">'tool_version\|server_version\|start_time\|end_time\|partial\|incremental'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Prepare the base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo xtrabackup --prepare --apply-log-only --no-server-version-check --target-dir&lt;span class="o">=&lt;/span>/data/backups/archive/archive_restore/data/backups/mysql/base
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Prepare each incremental folder. This must be done for each incremental folder we wish to back up to.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo xtrabackup --prepare --apply-log-only --no-server-version-check --target-dir&lt;span class="o">=&lt;/span>/data/backups/archive/archive_restore/data/backups/mysql/base --incremental-dir&lt;span class="o">=&lt;/span>/data/backups/archive/archive_restore/data/backups/mysql/inc_0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Repeat as necssary....&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Stop SQL and our backup script as we do not want it running mid restore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl stop mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl stop xtrabackupautomator.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Wipe bad/corrupted sql data from current instance&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo rm -rv /var/lib/mysql/*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Verify our mysql data is wiped&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo ls /var/lib/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># I use this method to restore my base backup, there are other options but they did not work correctly in my environment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo xtrabackup --copy-back --target-dir&lt;span class="o">=&lt;/span>/data/backups/archive/archive_restore/data/backups/mysql/base
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Verify the contents are the size we expect as a sanity check and apply the correct ownership to the files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ du -hs /var/lib/mysql/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chown -R mysql:mysql /var/lib/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Restart mysql and xtrabackupautomator. Verify MySQL's status&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl start mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl xtrabackupautomator.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo systemctl status mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>In an attempt to make this a one file, easy to install piece of software, I included the configuration struct in the xtrabackupautomator.py file, in the &lt;code>__init__&lt;/code> method of the XtraBackupAutomator class, on line ~60 (as of this writing). I will describe that struct, its default values, and other relevant information below. Most of this information can also be found in comments throughout, or in the getter methods for each variable.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== db ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -un
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: ""]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> XtraBackup user you set up during your initial configuration of Percona's XtraBackup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -pw
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: ""]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This user's password
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -host
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "localhost"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The IP of your database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -port
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 3306]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The port to access database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== folder_names ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -base_dir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "/data/backups/"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The root directory for all backup related things. Holds current backup and any archived backups.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This is the default location and is reflected in the setup as we request you create this folder.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If you change this directory in the config this change must be reflected in the setup.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -datadir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "mysql/"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Folder that current backups will be saved to. This would be the folder that holds the base backup and any
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> incremental backups before they are archived
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If you change this directory in the config this change must be reflected in the setup.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *** XtraBackupAutomator WILL ARCHIVE AND DELETE ANYTHING IN HERE. THIS SHOULD BE AN EMPTY FOLDER, NOT UTILIZED BY ANYTHING ELSE.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -archivedir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "archive/"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Folder that a group of backups will be archived to.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If you change this directory in the config this change must be reflected in the setup.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *** XtraBackupAutomator COULD POTENTIALLY DELETE ANY NON-DIRECTORY IN HERE.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== file_names ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -basefolder_name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "base"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Foldername for the base backup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -incrementalfolder_perfix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "inc_"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Folder name prefix for incremental backups.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Suffixed with the current number of incremental backups minus one
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> e.g., 'inc_0'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -archive_name_prefix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "database_backup_"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Prefix for the archive files.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Suffixed by the datetime of the archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> e.g., 'database_backup_11_28_2022__06_25_03.tar.gz'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== archive_settings ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -allow_archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: True]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> An override to enable/disable all archive settings.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Currently, disabling this will cause the program to do a base backup and then incremental backups forever.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -archive_zip_format
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "gztar"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The default archive file type. I like tarballs because they zip our large database into a manageable file.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> However, tarballs can take a long time to create and require a fair amount of resources if your DB is large.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This setting will depend on your system and the size of your DB. I recommend playing around with this.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Other zip options: [Shutil Man Page](https://docs.python.org/3/library/shutil.html#shutil.make_archive)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -archived_bu_count
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 7]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Keep x archived backups, once this threshold is reached the oldest archive will be deleted.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Archiving daily, this is a week of archives.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -enforce_max_num_bu_before_archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: True]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> One of two ways to 'force archive' of backups.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This counts the # of incremental backup folders and initiates the archives once that number is reached.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> A sample use case is that in your systemd timer file is scheduled to do 5 backups throughout the day, so setting this to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> true and max_num_bu_before_archive_count set to 4 (because we do not count the base) would give you a 'daily archive'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -max_num_bu_before_archive_count
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The max number of incremental backups to do before we archive (does not count the base).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Set to 0 to archive after each base
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -enforce_archive_at_time
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: False]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> One of two ways to 'force archive' of backups.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This will archive what ever base or incremental folders exist if a backup is happening within the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> archive_at_utc_24_hour hour. This is intended to make it easier to schedule when your archive and base backup occur.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> These can be resource intensive and so it is nice to do at off hours.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *If this program is scheduled to run more than once during the 'archive_at_utc_24_hour' hour each run will cause an archive.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -archive_at_utc_24_hour
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 6]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If a backup happens within this hour we will archive w/e was previously there and create a new base.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Matching this with a time setup in your xtrabackupautomator.timer allows you to choose when your backups will
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> occur.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> No explicit consideration for daylight savings time.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Defaults to the hour of 1:00am EST, 6:00am UTC.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== general_settings ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -backup_command_timeout_seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 30]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Give us 'backup_command_timeout_seconds' seconds for the command to respond.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This is not the same as saying 'a backup can only take this long'.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -max_time_between_backups_seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: 60*60*24]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Max number of seconds between this backup and the last.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If the last backup is older than this we will archive and create a new base.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This is in an attempt to prevent an incremental backup that might span days or weeks due to this service being
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> turned off or some such.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Defaults (arbitrarily) to 24 hours
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -additional_bu_command_params
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: ["no-server-version-check"]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Any additional parameters that you wish to pass along to your backup commands.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> We loop this list, put a '--' before each element and append it to the end of our backup commands.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This gets applied to the base and incremental backup commands.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> These are params that I have found useful.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">== log_settings ==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -is_enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: True]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enables/Disables all logging type settings. This was useful in testing, so I kept it around.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -log_child_process_to_screen
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: True]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If this is set to true the child process's output will be dumped to screen but not actually logged anywhere
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -is_log_to_file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: True]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> If set to True we will try to log to the 'default_log_file' in the 'default_log_path' directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -default_log_path
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "/var/log/"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The path that we will try to place our log file ('default_log_file')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -default_log_file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [DEFAULT_VALUE: "xtrabackupautomator.log"]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The file name we will try to log to&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="sources--links">Sources &amp; Links&lt;/h2>
&lt;ul>
&lt;li>Official Percona XtraBackup Documentation
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/index.html" target="_blank" rel="noopener noreferrer">https://docs.percona.com/percona-xtrabackup/8.0/index.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Systemctl Overveiw
&lt;ul>
&lt;li>&lt;a href="https://fedoramagazine.org/what-is-an-init-system/" target="_blank" rel="noopener noreferrer">https://fedoramagazine.org/what-is-an-init-system/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units" target="_blank" rel="noopener noreferrer">https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/codex/setup-a-python-script-as-a-service-through-systemctl-systemd-f0cc55a42267" target="_blank" rel="noopener noreferrer">https://medium.com/codex/setup-a-python-script-as-a-service-through-systemctl-systemd-f0cc55a42267&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Systemctl Timers Overview
&lt;ul>
&lt;li>&lt;a href="https://linuxconfig.org/how-to-schedule-tasks-with-systemd-timers-in-linux" target="_blank" rel="noopener noreferrer">https://linuxconfig.org/how-to-schedule-tasks-with-systemd-timers-in-linux&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://opensource.com/article/20/7/systemd-timers" target="_blank" rel="noopener noreferrer">https://opensource.com/article/20/7/systemd-timers&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Systemctl Services Details
&lt;ul>
&lt;li>&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html" target="_blank" rel="noopener noreferrer">https://www.freedesktop.org/software/systemd/man/systemd.service.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Systemctl Timers Details
&lt;ul>
&lt;li>&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.timer.html" target="_blank" rel="noopener noreferrer">https://www.freedesktop.org/software/systemd/man/systemd.timer.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>OnCalendar Expected Formats
&lt;ul>
&lt;li>&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.time.html#" target="_blank" rel="noopener noreferrer">https://www.freedesktop.org/software/systemd/man/systemd.time.html#&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Archive Zip Options
&lt;ul>
&lt;li>&lt;a href="https://docs.python.org/3/library/shutil.html#shutil.make_archive" target="_blank" rel="noopener noreferrer">https://docs.python.org/3/library/shutil.html#shutil.make_archive&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How to Extract (Unzip) Tar Gz File
&lt;ul>
&lt;li>&lt;a href="https://linuxize.com/post/how-to-extract-unzip-tar-gz-file/" target="_blank" rel="noopener noreferrer">https://linuxize.com/post/how-to-extract-unzip-tar-gz-file/&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Restoring Xtrabackup Incremental Backups
&lt;ul>
&lt;li>&lt;a href="https://docs.percona.com/percona-xtrabackup/8.0/backup_scenarios/incremental_backup.html" target="_blank" rel="noopener noreferrer">https://docs.percona.com/percona-xtrabackup/8.0/backup_scenarios/incremental_backup.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></content:encoded><author>Phil Plachta</author><category>XtraBackup</category><category>DevOps</category><media:thumbnail url="https://percona.community/blog/2023/01/automate_xtrabackup_hu_7a589919ed67f9a0.jpg"/><media:content url="https://percona.community/blog/2023/01/automate_xtrabackup_hu_4800e348e5210562.jpg" medium="image"/></item><item><title>Dashboard Story: How We Created PMM Dashboard for Highload</title><link>https://percona.community/blog/2022/12/22/dashboard-story-how-we-created-pmm-dashboard-for-highload/</link><guid>https://percona.community/blog/2022/12/22/dashboard-story-how-we-created-pmm-dashboard-for-highload/</guid><pubDate>Thu, 22 Dec 2022 00:00:00 UTC</pubDate><description>Let’s say you have highload instances. How do you monitor them? There are a lot of servers with 100, 200… 500+ nodes. How can we collect, check, and analyze metrics from all these servers? How can we understand what and where something happened? Scroll, scroll, scroll… down? That was the task that we faced at Percona and successfully resolved.</description><content:encoded>&lt;p>Let’s say you have highload instances. How do you monitor them? There are a lot of servers with 100, 200… 500+ nodes. How can we collect, check, and analyze metrics from all these servers? How can we understand what and where something happened? Scroll, scroll, scroll… down? That was the task that we faced at Percona and successfully resolved.&lt;/p>
&lt;h2 id="issue-with-home-dashboard">Issue With Home Dashboard&lt;/h2>
&lt;p>Percona Monitoring and Management dashboards are based on Grafana. So, when you opened PMM, you can see Grafana’s dashboards. Home Dashboard on the main page of PMM contains different metrics from all environments, databases and other resources. You can see here panels with current resources’ utilization - CPU, Memory, Disk Space, I/O Operations, Network. Certainly, these are very important metrics, which can help us quickly catch some issues… But after some years, we noticed that instances have more and more nodes. And we caught an issue with our Home Dashboard. At big instances (more than 100…200..etc nodes), there were performance issues. It was tough to understand what happened, and a user needed a lot of time for checking. From this point, we started our way.&lt;/p>
&lt;h2 id="searching-the-root-cause-of-our-issues">Searching the Root Cause of Our Issues&lt;/h2>
&lt;p>Before starting an investigation and searching for “bottleneck”, we defined some questions to answer first:&lt;/p>
&lt;ul>
&lt;li>What’s happened?&lt;/li>
&lt;li>Where to catch performance issues?&lt;/li>
&lt;li>How can we fix it?&lt;/li>
&lt;/ul>
&lt;p>Let’s answer these questions! To find out what’s happened, we need to check the response time for our Home Dashboard. So we created a PMM instance with 200 nodes for testing.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="856" height="722" sizes="(max-width: 856px) 100vw, 856px" srcset="https://percona.community/blog/2022/12/dashboard1_hu_7eea5cbc4eed55cf.webp 768w, https://percona.community/blog/2022/12/dashboard1_hu_efc5608e42ec4b09.webp 480w, https://percona.community/blog/2022/12/dashboard1_hu_ad7fbbc5b2635da.webp 856w" src="https://percona.community/blog/2022/12/dashboard1_hu_ad7fbbc5b2635da.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>What can we see here? Loading time is more than two minutes! Let’s dive deeper and check the longest requests.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1884" height="1024" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard2_hu_5276507c8ac2ba18.webp 768w, https://percona.community/blog/2022/12/dashboard2_hu_5901f6243babeb78.webp 480w, https://percona.community/blog/2022/12/dashboard2_hu_d699180f39a247e4.webp 1400w" src="https://percona.community/blog/2022/12/dashboard2_hu_d699180f39a247e4.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The longest time takes to get a request to VictoriaMetrics storage. If we try to scroll down, we can see “Lazy load” of our page and slower and slower working of our browser. Why? Because we request tons and tons of metrics.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2560" height="1328" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard3_hu_4e00e2230ebc0a50.webp 768w, https://percona.community/blog/2022/12/dashboard3_hu_f3e7b19167860d4d.webp 480w, https://percona.community/blog/2022/12/dashboard3_hu_e4d46484608d45d2.webp 1400w" src="https://percona.community/blog/2022/12/dashboard3_hu_e4d46484608d45d2.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And it seems that we caught here our main problem - too much data, too many requests, and too many responses. What can we do? We decided to create a new Home Dashboard!&lt;/p>
&lt;h2 id="strategies-for-creating-a-dashboard">Strategies for Creating a Dashboard&lt;/h2>
&lt;p>There are a lot of strategies for creating dashboards. But we need a short, informative, user-friendly one. Our final goal is to provide a simple answer for the questions: “Is it all good? I want to drink my morning coffee” or “Is something bad? We need to repair it ASAP!”&lt;/p>
&lt;p>Let’s investigate what we can do here.&lt;/p>
&lt;p>In the O’Reilly &lt;a href="https://www.amazon.com/Site-Reliability-Engineering-Production-Systems/dp/149192912X" target="_blank" rel="noopener noreferrer">Site Reliability Engineering&lt;/a> book, we can read about four golden signals strategy: Latency, Traffic, Errors and Saturation. Let’s meet each of these signals.&lt;/p>
&lt;h3 id="latency">Latency&lt;/h3>
&lt;p>Latency means the time it takes to service a request. One important moment — differences between successful and unsuccessful requests.&lt;/p>
&lt;p>For example, an HTTP 500 error means that the connection was lost and this error served very quickly, however, as an HTTP 500 error indicates a failed request, factoring 500s into your overall latency might result in misleading calculations. On the other hand, a slow error is even worse than a fast error! Therefore, it’s important to track error latency, as opposed to just filtering out errors.&lt;/p>
&lt;h3 id="traffic">Traffic&lt;/h3>
&lt;p>Traffic is a measure of how much demand is being placed on your system. For web service, it is usually HTTP requests per second, for audio may be network I/O rate, for key-value storage systems — transactions and retrievals per second.&lt;/p>
&lt;h3 id="errors">Errors&lt;/h3>
&lt;p>Errors are the rate of requests that fail, either explicitly (e.g., HTTP 500s), or implicitly (for example, an HTTP 200 success response, but coupled with the wrong content).&lt;/p>
&lt;p>Monitoring these cases can be drastically different: catching HTTP 500s at your load balancer can do a decent job of catching all completely failed requests, while only end-to-end system tests can detect that you’re serving the wrong content.&lt;/p>
&lt;h3 id="saturations">Saturations&lt;/h3>
&lt;p>It is a measure of your system fraction, emphasizing the resources that are most constrained (e.g., in a memory-constrained system, show memory; in an I/O-constrained system, show I/O). Note that many systems degrade in performance before they achieve 100% utilization, so having a utilization target is essential.&lt;/p>
&lt;p>If you measure all four golden signals and call for a human when one signal is problematic (or, in the case of saturation, nearly problematic), your service will be at least decently covered by monitoring.&lt;/p>
&lt;p>There are also &lt;strong>USE&lt;/strong> and &lt;strong>RED&lt;/strong> strategies that we took into consideration.&lt;/p>
&lt;p>R — Rate, request per second
E — Errors, how many request return error
D — Duration, latency, the time it takes to service a request&lt;/p>
&lt;p>U — utilization, how fully resource working
S — saturation, how long queue at this resources
E — errors, how many errors do we have?&lt;/p>
&lt;h2 id="poc-of-home-dashboard">POC of Home Dashboard&lt;/h2>
&lt;p>All these strategies are interesting and helpful. But we want to compile the best for our dashboard. Our Tech Lead Dani Guzmán Burgos created a Proof-of-Concept (POC) of our new Home Dashboard. Our main idea is a simple answer to the questions “all good” or “is something bad.”&lt;/p>
&lt;p>When you open this dashboard, you can see simple color panels — green or red. How do we measure this?&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2046" height="1206" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard4_hu_f360bc41762d4d47.webp 768w, https://percona.community/blog/2022/12/dashboard4_hu_eca9b9b8e6650a0.webp 480w, https://percona.community/blog/2022/12/dashboard4_hu_9ae240ef95752ea.webp 1400w" src="https://percona.community/blog/2022/12/dashboard4_hu_9ae240ef95752ea.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Here we can see common information about our environment: how many nodes we have, disk operations, DB and node uptime, and advisors’ checks. There is also a very interesting panel with the name “Environment Health,” which is our secret feature.&lt;/p>
&lt;p>For anomaly detection, we use CPU and disk metrics. And here, we also answer questions about how fully our resources are working and what duration we have. On the right panels, we can see data with 15 minutes relative time (to prevent peaks and performance issues). On the left side, we can compare current metrics with metrics from a week ago to measure trends.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1474" height="1206" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard5_hu_485d33f0d7dd1056.webp 768w, https://percona.community/blog/2022/12/dashboard5_hu_a5e404ec3d0fbe8b.webp 480w, https://percona.community/blog/2022/12/dashboard5_hu_94bc921b49c25230.webp 1400w" src="https://percona.community/blog/2022/12/dashboard5_hu_94bc921b49c25230.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In the Command center, we can find more details about what’s wrong. There are three kinds of panels: current usage, anomalies, and metrics for one week ago.&lt;/p>
&lt;p>As main metrics, we use CPU, disk queue, write latency, read latency and used memory. These metrics can very quickly help us understand what’s happened in our system.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1734" height="750" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard6_hu_a7f0b39aa75b63e2.webp 768w, https://percona.community/blog/2022/12/dashboard6_hu_89251b73a31374db.webp 480w, https://percona.community/blog/2022/12/dashboard6_hu_44551973feb071bf.webp 1400w" src="https://percona.community/blog/2022/12/dashboard6_hu_44551973feb071bf.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>And finally, the panel Service Summary shows detailed information about each service (node, server) in our system: number of connections to DB, QPS at each of them, and uptime.&lt;/p>
&lt;h2 id="polishing-the-dashboard---feedback-matters">Polishing the Dashboard - Feedback Matters&lt;/h2>
&lt;p>When we discussed the POC with other teams, we got the question, “what does it mean — No anomalies?” Then we added a detailed description “No alerts because CPU less than xx percent.” Sounds better, doesn’t it?&lt;/p>
&lt;p>
&lt;figure>&lt;img width="930" height="512" sizes="(max-width: 930px) 100vw, 930px" srcset="https://percona.community/blog/2022/12/dashboard7_hu_9eadbee35507214c.webp 768w, https://percona.community/blog/2022/12/dashboard7_hu_5054002f936980c3.webp 480w, https://percona.community/blog/2022/12/dashboard7_hu_5b835a07ae339511.webp 930w" src="https://percona.community/blog/2022/12/dashboard7_hu_5b835a07ae339511.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our previous dashboard looked good, but we wanted more! What could we improve? We already have CPU, Disk anomalies, maybe we can add more metrics here? And we did! High memory? Perfect! Also, to prevent paying a lot for unused hardware, we implemented “Low CPU Servers” where we get alerts when using less than 30 CPUs.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1706" height="336" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard8_hu_c1c353ed1a3d4b7c.webp 768w, https://percona.community/blog/2022/12/dashboard8_hu_a80ba952a46f16fb.webp 480w, https://percona.community/blog/2022/12/dashboard8_hu_bcd89f3661841012.webp 1400w" src="https://percona.community/blog/2022/12/dashboard8_hu_bcd89f3661841012.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>When we have red statuses for nodes in the Anomaly Detection section, we can explore it and drill down. We can jump to a more detailed level and check what happened - CPU, Disk, and Memory for each metric.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1698" height="604" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard9_hu_5df475b040b63e4.webp 768w, https://percona.community/blog/2022/12/dashboard9_hu_284e37486ca8290.webp 480w, https://percona.community/blog/2022/12/dashboard9_hu_256578988ef49367.webp 1400w" src="https://percona.community/blog/2022/12/dashboard9_hu_256578988ef49367.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The first version of Overview was changed too. We added more details about different databases. Some panels were removed after feedback. And the main feature is filtering.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1816" height="118" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/12/dashboard10_hu_689c0371a82a9b22.webp 768w, https://percona.community/blog/2022/12/dashboard10_hu_c808f614a81fc078.webp 480w, https://percona.community/blog/2022/12/dashboard10_hu_f035c8868c3f65d6.webp 1400w" src="https://percona.community/blog/2022/12/dashboard10_hu_f035c8868c3f65d6.webp" alt="Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Here we tried to create a view where a user can choose the environment and see only its nodes.&lt;/p>
&lt;p>That’s how we achieved our final goal - you can open the dashboard, check it, and then drink your morning cup of coffee with a calm mind!&lt;/p>
&lt;p>Try this out if you’re already using Percona PMM. If you’re not? You can set up and try out PMM in just a few minutes, start with the &lt;a href="https://www.percona.com/software/pmm/quickstart" target="_blank" rel="noopener noreferrer">Quickstart&lt;/a>."&lt;/p></content:encoded><author>Anton Bystrov</author><author>Aleksandra Abramova</author><category>PMM</category><category>monitoring</category><category>dashboard</category><category>VictoriaMetrics</category><media:thumbnail url="https://percona.community/blog/2022/12/Dashboards-PMM_hu_8ff5fb4af433d313.jpg"/><media:content url="https://percona.community/blog/2022/12/Dashboards-PMM_hu_c783eefe696393dd.jpg" medium="image"/></item><item><title>Testing Kubernetes with KUTTL</title><link>https://percona.community/blog/2022/12/16/testing-kubernetes-with-kuttl/</link><guid>https://percona.community/blog/2022/12/16/testing-kubernetes-with-kuttl/</guid><pubDate>Fri, 16 Dec 2022 00:00:00 UTC</pubDate><description>Automated testing is the only way to be sure that your code works. Enabling automated testing can be hard and we say a lot of tools to write automated tests in the industry since the beginning. Some veterans in the industry may remember Selenium, Cucumber frameworks that help automate testing in the browser. However, testing in Kubernetes can be hard.</description><content:encoded>&lt;p>Automated testing is the only way to be sure that your code works. Enabling automated testing can be hard and we say a lot of tools to write automated tests in the industry since the beginning. Some veterans in the industry may remember Selenium, Cucumber frameworks that help automate testing in the browser. However, testing in Kubernetes can be hard.&lt;/p>
&lt;p>In Percona we deal with Kubernetes and have different operators to automate the management of databases. It requires testing. A lot of testing. We have different frameworks to help us with it&lt;/p>
&lt;ol>
&lt;li>Codecept.js to write UI tests for PMM. Also, we use a playwright for some cases.&lt;/li>
&lt;li>We have tools to help us with API testing as well as automating some routines by running bash commands during the test step.&lt;/li>
&lt;/ol>
&lt;p>However, those frameworks are not applicable to test Kubernetes workloads as well as Kubernetes operators. I’ve been working in the PMM integrations team for six months and saw different approaches to automate testing for PMM/DBaaS. We have a Go test library with wrappers around kubectl and codecept.js for end-to-end tests for the User Interface.&lt;/p>
&lt;h2 id="what-challenges-do-we-have">What challenges do we have?&lt;/h2>
&lt;p>Well, to be sure that a database cluster creation works we need to automate the following steps&lt;/p>
&lt;ol>
&lt;li>Installation of operators to Kubernetes cluster&lt;/li>
&lt;li>Test integration with version service to respect compatibility matrix.&lt;/li>
&lt;li>Create a database cluster and wait once it’ll be available&lt;/li>
&lt;li>Do some assertions against Kubernetes as well as UI.&lt;/li>
&lt;/ol>
&lt;p>The main pain point here is that we need to wait up to 10-15 minutes for each step and we can’t have different test cases to cover as many cases as we can. Yet we can achieve some performance benefits by paralleling workloads, still, it requires learning Javascript and testing framework to work with it. We had some architectural changes recently and moved from our custom gRPC API to create and manage database clusters to an operator that runs on top of other operators and converts K8s’ Custom Resource from generic format to operator specific. We had a couple of options for this new project and after research, we chose kuttl as a framework for integration/e2e testing.&lt;/p>
&lt;h2 id="what-is-kuttl-anyway-and-why-should-i-care">What is KUTTL anyway and why should I care?&lt;/h2>
&lt;p>KUTTL is the KUbernetes Test TooL. It’s written in Go and provides a declarative way to test Kubernetes operators using Kubernetes primitives. It’s easy to start kuttling. Let’s take a deeper look. I’ll use &lt;a href="https://github.com/percona/dbaas-operator" target="_blank" rel="noopener noreferrer">dbaas-operator&lt;/a> as an example. DBaaS-operator is an operator that has a simple and generic Custom Resource Definition available to create Percona Server MongoDB or Percona XtraDB Cluster instances in kubernetes. It uses underlying operators as a dependencies. We have the following structure&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">e2e-tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kind.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kuttl-eks.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kuttl.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── pxc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 00-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 00-deploy-operators.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 01-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 01-deploy-pxc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 02-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 02-upgrade-pxc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 03-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 03-restart-pxc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 04-delete-cluster.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 05-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 05-create-cluster.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 06-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 06-scale-up-pxc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 07-assert.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 07-scale-down-pxc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 08-delete-cluster.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2 directories, 19 files&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s discuss these YAML files more&lt;/p>
&lt;ol>
&lt;li>kind.yml contains settings to run &lt;a href="https://kind.sigs.k8s.io/" target="_blank" rel="noopener noreferrer">Kind&lt;/a>&lt;/li>
&lt;li>kuttl.yml has all required settings for Kuttl framework and kuttl-eks.yml has some EKS specific configurations&lt;/li>
&lt;li>tests folder has test steps and assertions&lt;/li>
&lt;/ol>
&lt;h2 id="kind-and-kuttl-settings">Kind and KUTTL settings&lt;/h2>
&lt;p>Let’s discuss Kind and kuttl settings and I’ll start with KUTTL first&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kuttl.dev/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TestSuite&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kindConfig&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">e2e-tests/kind.yml &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Path to Kind config that will be used to create Kind clusters&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">crdDir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">config/crd/bases &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Path to a directory that contains CRD files. Kuttl will apply them before running tests&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">artifactsDir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/tmp/ &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Path to a directory to store artifacts such as logs and other information&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">testDirs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="l">e2e-tests/tests &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Path to directories that have test steps&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Kind config is quite easy&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kind.x-k8s.io/v1alpha4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">nodes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">role&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">control-plane&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">role&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">worker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">role&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">worker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">role&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">worker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">containerdConfigPatches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="p">|-&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> endpoint = ["http://kind-registry:5000"]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The aforementioned config will use local registry and will create 3 k8s worker nodes controlled by control plane&lt;/p>
&lt;h2 id="writing-tests">Writing tests&lt;/h2>
&lt;p>At the first glance, kuttling can be easy because it uses Kubernetes primitives as a test step and assertion but I had a couple of problems to test my operator. Let’s take a look at a couple of examples. Since dbaas-operator depends on PXC operator we need to prepare our environment for testing. Let’s write first test that installs PXC operator and ensures that it was installed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cat e2e-tests/tests/pxc/00-deploy-pxc-operator.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: kuttl.dev/v1beta1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: TestStep
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeout: 10 # Timeout for the test step
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commands:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - command: kubectl apply -f https://raw.githubusercontent.com/percona/percona-xtradb-cluster-operator/v${PXC_OPERATOR_VERSION}/deploy/bundle.yaml -n "${NAMESPACE}"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>KUTTL test steps easily extensible with &lt;a href="https://kuttl.dev/docs/testing/reference.html#commands" target="_blank" rel="noopener noreferrer">commands&lt;/a>. One can run even scripts as a prerequisite for a test case. PXC operator installs CRDs and creates a deployment and here’s an example of assertion.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cat e2e-tests/tests/pxc/00-assert.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: kuttl.dev/v1beta1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: TestAssert
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeout: 120 # Timeout waiting for the state
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: apiextensions.k8s.io/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: CustomResourceDefinition
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: perconaxtradbclusters.pxc.percona.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> group: pxc.percona.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> names:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind: PerconaXtraDBCluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listKind: PerconaXtraDBClusterList
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> plural: perconaxtradbclusters
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> shortNames:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - pxc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - pxcs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> singular: perconaxtradbcluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> scope: Namespaced
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: apiextensions.k8s.io/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: CustomResourceDefinition
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: databaseclusters.dbaas.percona.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> group: dbaas.percona.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> names:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind: DatabaseCluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listKind: DatabaseClusterList
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> plural: databaseclusters
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> shortNames:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> singular: databasecluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> scope: Namespaced
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: apps/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Deployment
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: percona-xtradb-cluster-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">status:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> availableReplicas: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> observedGeneration: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> readyReplicas: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicas: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> updatedReplicas: 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Our first test is ready and one needs to run &lt;code>kubectl kuttl test --config ./e2e-tests/kuttl.yml&lt;/code> to run kuttl.&lt;/p>
&lt;h2 id="more-advanced-tests">More advanced tests&lt;/h2>
&lt;p>We need to run our operator first to be able to work with resources and test it. KUTTL recomends configure it via &lt;code>TestSuite&lt;/code> by the following example&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kuttl.dev/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TestSuite&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">commands&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">./bin/manager&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>However, since dbaas-operator depends on underlying operators it needs to work correctly even if they are not present in a Kubernetes cluster. It has the following logic in the controller&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">// SetupWithManager sets up the controller with the Manager.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">func (r *DatabaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fmt.Println(os.Getenv("WATCH_NAMESPACE"))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unstructuredResource := &amp;unstructured.Unstructured{}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unstructuredResource.SetGroupVersionKind(schema.GroupVersionKind{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Group: "apiextensions.k8s.io",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Kind: "CustomResourceDefinition",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Version: "v1",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> controller := ctrl.NewControllerManagedBy(mgr).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> For(&amp;dbaasv1.DatabaseCluster{})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> err := r.Get(context.Background(), types.NamespacedName{Name: pxcCRDName}, unstructuredResource)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if err == nil {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if err := r.addPXCToScheme(r.Scheme); err == nil {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> controller.Owns(&amp;pxcv1.PerconaXtraDBCluster{})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> err = r.Get(context.Background(), types.NamespacedName{Name: psmdbCRDName}, unstructuredResource)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if err == nil {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if err := r.addPSMDBToScheme(r.Scheme); err == nil {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> controller.Owns(&amp;psmdbv1.PerconaServerMongoDB{})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return controller.Complete(r)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>controller.Owns&lt;/code> sets up a controller to watch specified resources and once they were changed it’ll run a reconciliation loop to sync changes. Also, it checks that operator is present in the cluster by checking that deployment and CRDs are available. It means that to make the operator work correctly in tests we need to choose from the following options&lt;/p>
&lt;ol>
&lt;li>Restart operator once upsteam operator was installed by sending &lt;code>HUP&lt;/code> signal&lt;/li>
&lt;li>Run operator only after underlying operator is present in a cluster&lt;/li>
&lt;/ol>
&lt;p>Hence, I moved command as the next step before creating a cluster. You can see it below&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cat e2e-tests/tests/pxc/01-deploy-pxc.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: kuttl.dev/v1beta1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: TestStep
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeout: 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commands:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - script: WATCH_NAMESPACE=$NAMESPACE ../../../bin/manager
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> background: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apiVersion: dbaas.percona.com/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: DatabaseCluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">metadata:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: test-cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">spec:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> databaseType: pxc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> databaseImage: percona/percona-xtradb-cluster:8.0.23-14.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> databaseConfig: |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [mysqld]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wsrep_provider_options="debug=1;gcache.size=1G"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secretsName: pxc-sample-secrets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> clusterSize: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> loadBalancer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: haproxy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> exposeType: ClusterIP
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> size: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: percona/percona-xtradb-cluster-operator:1.11.0-haproxy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dbInstance:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cpu: "1"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> memory: 1G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> diskSize: 15G&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: &lt;code>command&lt;/code> supports only simple commands and does not fully support env variables. It supports only $NAMESPACE, $PATH and $HOME. However, &lt;code>script&lt;/code> solves the problem of setting &lt;code>WATCH_NAMESPACE&lt;/code> environment variable.&lt;/p>
&lt;p>In nutshell, the test step above does two things:&lt;/p>
&lt;ol>
&lt;li>Runs the operator&lt;/li>
&lt;li>Creates a database cluster&lt;/li>
&lt;/ol>
&lt;p>The assertion checks that kubernetes cluster has the &lt;code>DatabaseCluster&lt;/code> object with &lt;code>ready&lt;/code> status as well as PXC cluster with the same status.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kuttl.dev/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TestAssert&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">600&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dbaas.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DatabaseCluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">databaseType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pxc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">databaseImage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-xtradb-cluster:8.0.23-14.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">databaseConfig&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> [mysqld]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> wsrep_provider_options="debug=1;gcache.size=1G"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">secretsName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pxc-sample-secrets&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterSize&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">loadBalancer&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">haproxy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exposeType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterIP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-xtradb-cluster-operator:1.11.0-haproxy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dbInstance&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"1"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">diskSize&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">15G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pxc.percona.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PerconaXtraDBCluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allowUnsafeConfigurations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">crVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.11.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">haproxy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-xtradb-cluster-operator:1.11.0-haproxy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterIP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pxc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">configuration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> [mysqld]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> wsrep_provider_options="debug=1;gcache.size=1G"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expose&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona/percona-xtradb-cluster:8.0.23-14.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">livenessProbes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readinessProbes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"1"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterIP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">sidecarResources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeSpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">persistentVolumeClaim&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">15G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">secretsName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pxc-sample-secrets&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">updateStrategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">SmartUpdate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">upgradeOptions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apply&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8.0&lt;/span>-&lt;span class="l">recommended&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w"> &lt;/span>*&lt;span class="w"> &lt;/span>*&lt;span class="w"> &lt;/span>*&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">status&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ready&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">state&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ready&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="caveats-and-notes">Caveats and notes&lt;/h2>
&lt;p>I had problems running tests in Kind. They were flaky because PXC operator can’t expose metrics and had problems with liveness probe. I haven’t figured out how to fix it but as a workaround I use minikube to run tests&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> minikube start --nodes=4 --cpus=2 --memory=4g --apiserver-names host.docker.internal --kubernetes-version=v1.23.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> minikube kubectl -- config view --flatten --minify > ~/.kube/test-minikube
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> KUBECONFIG=~/.kube/test-minikube kubectl kuttl test --config ./e2e-tests/kuttl.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="further-steps">Further steps&lt;/h2>
&lt;p>There’s always room for improvement and I have these steps in mind&lt;/p>
&lt;ol>
&lt;li>Use docker images and OLM bundles as a way to run the operator for tests. This will be the best way to simulate a production like environment.&lt;/li>
&lt;li>Add more advanced tests for database clusters such as running queries, try loading data as well as capacity testing. It’s easily achivable with kuttl&lt;/li>
&lt;/ol></content:encoded><author>Andrew Minkin</author><category>PMM</category><category>DBaaS</category><category>KUTTL</category><category>testing</category><media:thumbnail url="https://percona.community/blog/2022/12/K8S-KUTTL_hu_89a8b6eb1df21ae3.jpg"/><media:content url="https://percona.community/blog/2022/12/K8S-KUTTL_hu_848f0ae66b9be622.jpg" medium="image"/></item><item><title>How To Generate Data With Pagila in Percona Distribution for PostgreSQL</title><link>https://percona.community/blog/2022/12/13/how-to-generate-data-with-pagila-in-percona-distribution-for-postgresql/</link><guid>https://percona.community/blog/2022/12/13/how-to-generate-data-with-pagila-in-percona-distribution-for-postgresql/</guid><pubDate>Tue, 13 Dec 2022 00:00:00 UTC</pubDate><description>Have you ever faced the need to generate test data for your Postgres database? I am sure you have! This blog post will guide you step-by-step through one of the many ways to get it fast and easy. That will leave you plenty of time to focus on queries. No need to spend time creating your data generation scripts!</description><content:encoded>&lt;p>Have you ever faced the need to generate test data for your Postgres database? I am sure you have! This blog post will guide you step-by-step through one of the many ways to get it fast and easy. That will leave you plenty of time to focus on queries. No need to spend time creating your data generation scripts!&lt;/p>
&lt;p>For this guide, we will use &lt;a href="https://github.com/devrimgunduz/pagila" target="_blank" rel="noopener noreferrer">Pagila&lt;/a>, a tool that provides a standard schema that we can use for examples in books, tutorials, articles, samples, etc. The project is open source; you can clone it and start to run the queries. With Pagila, we will create all schema objects and insert the data into our tables.&lt;/p>
&lt;p>We will use &lt;a href="https://www.percona.com/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> 12.13 or higher as a database. It is an easy but powerful way to implement an enterprise-grade, fully open source PostgreSQL environment.&lt;/p>
&lt;p>Let’s start it!&lt;/p>
&lt;h2 id="requirements">Requirements&lt;/h2>
&lt;ul>
&lt;li>Docker
&lt;ul>
&lt;li>You can install Docker by following this &lt;a href="https://docs.docker.com/engine/install/ubuntu/" target="_blank" rel="noopener noreferrer">guide&lt;/a>.&lt;/li>
&lt;li>Manage Docker as a non-root user: &lt;strong>&lt;em>sudo usermod -aG docker $USER&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="installing-percona-distribution-for-postgresql">Installing Percona Distribution for PostgreSQL&lt;/h2>
&lt;ol>
&lt;li>On your terminal, pull the Percona Distribution for PostgreSQL image. I am using the 12.13 version.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker pull perconalab/percona-distribution-postgresql:12.13&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Run Percona Distribution PostgreSQL container. You must specify POSTGRES_PASSWORD as a non-empty value for the superuser.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run --name percona-postgres -e &lt;span class="nv">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>secret -d perconalab/percona-distribution-postgresql:12.13&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="using-pagila-to-generate-data">Using Pagila to Generate Data&lt;/h2>
&lt;ol>
&lt;li>Clone the Pagila GitHub repository.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/devrimgunduz/pagila&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Enter in the Percona Distribution Postgresql container.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-postgres psql -U postgres&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Create a database called perconadb&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE DATABASE perconadb&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\q&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Create all schema objects (tables, etc.):&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> pagila&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We will execute the script pagila-schema.sql inside percona-postgres container. The script will create the schemas objects like tables, views, functions, and constraints&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat pagila-schema.sql &lt;span class="p">|&lt;/span> docker &lt;span class="nb">exec&lt;/span> -i percona-postgres psql -U postgres -d perconadb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="252" height="438" sizes="(max-width: 252px) 100vw, 252px" srcset="https://percona.community/blog/2022/12/pagila-schema-output_hu_751955a870ea7164.webp 252w" src="https://percona.community/blog/2022/12/pagila-schema-output_hu_751955a870ea7164.webp" alt="Output" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="5">
&lt;li>Insert all data.
We will execute pagila-data.sql script inside the container to insert data in all the tables we created before.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat pagila-data.sql &lt;span class="p">|&lt;/span> docker &lt;span class="nb">exec&lt;/span> -i percona-postgres psql -U postgres -d perconadb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="234" height="593" sizes="(max-width: 234px) 100vw, 234px" srcset="https://percona.community/blog/2022/12/pagila-data-output_hu_b728ae055ce6a3b6.webp 234w" src="https://percona.community/blog/2022/12/pagila-data-output_hu_b728ae055ce6a3b6.webp" alt="Output" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Validate the data; let’s check if the tables were created and if it is populated with data.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it percona-postgres psql -U postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\c&lt;/span> perconadb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\d&lt;/span>t&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="505" height="271" sizes="(max-width: 505px) 100vw, 505px" srcset="https://percona.community/blog/2022/12/dt-output_hu_173a4c73d6ba5af1.webp 480w, https://percona.community/blog/2022/12/dt-output_hu_1e8d32a0cb7088a6.webp 505w" src="https://percona.community/blog/2022/12/dt-output_hu_1e8d32a0cb7088a6.webp" alt="Output" loading="lazy" />&lt;/figure>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">postgresql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-postgresql" data-lang="postgresql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="k">FROM&lt;/span> &lt;span class="n">inventory&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="521" height="323" sizes="(max-width: 521px) 100vw, 521px" srcset="https://percona.community/blog/2022/12/inventory-output_hu_431acdfd61b99c50.webp 480w, https://percona.community/blog/2022/12/inventory-output_hu_77c7c7dad99c9645.webp 521w" src="https://percona.community/blog/2022/12/inventory-output_hu_77c7c7dad99c9645.webp" alt="Output" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You are ready! The data is there and ready to be used.
You can refer to the official documentation of &lt;a href="https://www.percona.com/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> if you want to know the entire collection of tools to help you manage your PostgreSQL database system.
You can check &lt;a href="https://github.com/devrimgunduz/pagila" target="_blank" rel="noopener noreferrer">Pagila&lt;/a> open source project to generate data for Postgres.&lt;/p></content:encoded><author>Edith Puclla</author><category>Docker</category><category>PostgreSQL</category><category>Pagila</category><category>Database</category><media:thumbnail url="https://percona.community/blog/2022/12/percona-pagila_hu_936787cbf939949e.jpg"/><media:content url="https://percona.community/blog/2022/12/percona-pagila_hu_53fafc5fd17d4a9c.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.33 preview release</title><link>https://percona.community/blog/2022/12/08/preview-release/</link><guid>https://percona.community/blog/2022/12/08/preview-release/</guid><pubDate>Thu, 08 Dec 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.33 preview release Hello folks! Percona Monitoring and Management (PMM) 2.33 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-233-preview-release">Percona Monitoring and Management 2.33 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.33 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release notes can be found in &lt;a href="https://pmm-2-33-0.onrender.com/release-notes/2.33.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker">Percona Monitoring and Management server docker&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.33.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.33.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.33 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4615.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.33.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.33.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-005acacf35adcfa57&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.32 preview release</title><link>https://percona.community/blog/2022/11/04/preview-release/</link><guid>https://percona.community/blog/2022/11/04/preview-release/</guid><pubDate>Fri, 04 Nov 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.32 preview release Hello folks! Percona Monitoring and Management (PMM) 2.32 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-232-preview-release">Percona Monitoring and Management 2.32 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.32 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release notes can be found in &lt;a href="https://pmm-doc-2-32-pr-904.onrender.com/release-notes/2.32.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker">Percona Monitoring and Management server docker&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.32.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.32.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.32 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4500.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;p>&lt;code>percona-release enable percona testing&lt;/code>&lt;/p>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://percona-vm.s3.amazonaws.com/PMM2-Server-2.32.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.32.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-02cfe7580e77fb5fa&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>AWS Summit Mexico City: Back to In-Person Events</title><link>https://percona.community/blog/2022/10/19/aws-summit-mexico-city-back-to-in-person-events/</link><guid>https://percona.community/blog/2022/10/19/aws-summit-mexico-city-back-to-in-person-events/</guid><pubDate>Wed, 19 Oct 2022 00:00:00 UTC</pubDate><description>In March 2020, I presented my latest talk at an in-person event. This event was organized by a local university, Then when events started to happen online, during the pandemic, I was a speaker at about 41 events, where I presented 38 talks and 7 workshops (until September this year), in both Spanish and English.</description><content:encoded>&lt;p>In March 2020, I presented my latest talk at an in-person event. This event was organized by a local university, Then when events started to happen online, during the pandemic, I was a speaker at about 41 events, where I presented 38 talks and 7 workshops (until September this year), in both Spanish and English.&lt;/p>
&lt;p>Having the opportunity to join so many events and collaborate with communities around the world was one of the advantages that events were organized in virtual spaces. I met awesome people doing amazing things, and I learned a lot from them. But I really was missing attending in-person events, especially those being held in other cities.&lt;/p>
&lt;p>On July 12, I joined Percona as a Technical Evangelist, and two months later I was at the airport, in my city, waiting for my flight to my first in-person event after 2 and a half years, and the first one as Perconian. &lt;a href="http://aws.amazon.com/es/events/summits/mexico-city/" target="_blank" rel="noopener noreferrer">AWS Summit Mexico City&lt;/a> was held on September 21 and September 22 at &lt;a href="https://www.exposantafe.com.mx/esfm/" target="_blank" rel="noopener noreferrer">Expo Santa Fe Mexico&lt;/a>.&lt;/p>
&lt;p>The last time I visited the Expo I was attending Campus Party Mexico 2013, with the Firefox OS launch team, for a pre-launch event, and my last trip to Mexico City was in October 2019, before leaving for London to speak at GitLab Commit London.&lt;/p>
&lt;p>I was at AWS Summit Mexico City just as an attendee, looking forward to learning more about AWS, doing some networking, and meeting some friends I hadn’t seen in a long time or never seen in person.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_3dafb6502418ecee.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_bd10ef7f764de707.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_355ea4562f7605e3.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_355ea4562f7605e3.webp" alt="AWS Summit Mexico City" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="the-adventure-had-begun">The Adventure Had Begun&lt;/h2>
&lt;p>I booked my flight for September 20 at 9 PM, the only non-stop flight available. I arrived early at the airport, just in time for boarding. It was raining, so take-off was delayed until 10 PM. The plane landed in Mexico City at 11:30 PM.&lt;/p>
&lt;p>My hotel was in &lt;a href="https://en.wikipedia.org/wiki/Santa_Fe,_Mexico_City" target="_blank" rel="noopener noreferrer">Santa Fe&lt;/a>, near the location of the event. Once I arrived I took an Uber and after 35 minutes I was at the hotel. But to my surprise my reservation was cancelled, and there were no rooms available. There was a problem with their payment system and my debit card was declined.&lt;/p>
&lt;p>Had to call other hotels nearby, and finally found one with rooms available, 8 minutes from the Expo. I got a better deal: a lower cost, and a bigger room. Slept just four hours but I was ready for the event.&lt;/p>
&lt;h2 id="visiting-booth">Visiting Booth&lt;/h2>
&lt;p>On the first day, I arrived early to the Expo for registering and getting my badge. The expo zone would open at 8 AM, so I had to wait. There were booth by sponsors and AWS.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="12000" height="9000" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_dfb283d96cdce10.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_2b7bb1a16468421c.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_f18c7f1f796955cc.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_f18c7f1f796955cc.webp" alt="AWS Summit Mexico City Expo" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>During the first day these were the booths I visited:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://vmware.com/" target="_blank" rel="noopener noreferrer">VMWare&lt;/a>: When I was in college I tested VMWare for learning about virtualization, and how to install Linux on other operating systems. I haven’t used it since then, but it was interesting to know that VMWare now has some other tools and cloud services like &lt;a href="https://cloudhealth.vmware.com/" target="_blank" rel="noopener noreferrer">Cloud Health&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://datadog.com/" target="_blank" rel="noopener noreferrer">Datadog&lt;/a> / &lt;a href="https://dynatrace.com/" target="_blank" rel="noopener noreferrer">Dynatrace&lt;/a>: While passing by Datadog and Dynatrace booths I had the opportunity to watch a demo of their platforms, specifically those monitoring and observability features they provide for databases, and a wide range of different technologies.&lt;/p>
&lt;p>On Datadog integration with AWS you can get:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://docs.datadoghq.com/infrastructure/" target="_blank" rel="noopener noreferrer">Infrastructure Monitoring&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.datadoghq.com/tracing/" target="_blank" rel="noopener noreferrer">App Performance Monitoring (APM)&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.datadoghq.com/logs/" target="_blank" rel="noopener noreferrer">Log Management&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.datadoghq.com/security_platform/" target="_blank" rel="noopener noreferrer">Security Monitoring&lt;/a>&lt;/p>
&lt;p>More information available in the &lt;a href="https://docs.datadoghq.com/" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Dynatrace provides monitoring features for the following areas:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.dynatrace.com/platform/applications-microservices-monitoring/" target="_blank" rel="noopener noreferrer">Applications and Microservices&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.dynatrace.com/platform/infrastructure-monitoring/" target="_blank" rel="noopener noreferrer">Infrastructure Monitoring&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.dynatrace.com/platform/digital-experience/" target="_blank" rel="noopener noreferrer">Digital Experience&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.dynatrace.com/support/help/how-to-use-dynatrace/databases" target="_blank" rel="noopener noreferrer">Database Monitoring&lt;/a>&lt;/p>
&lt;p>More information available in the &lt;a href="https://www.dynatrace.com/platform/" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://hashicorp.com/" target="_blank" rel="noopener noreferrer">HashiCorp&lt;/a>: I’ve been a &lt;a href="https://www.hashicorp.com/ambassadors" target="_blank" rel="noopener noreferrer">HashiCorp Ambassador&lt;/a> since 2021 and as part of the program I’ve been creating content related to Vagrant and Packer, including blog posts and a few talks I’ve presented at virtual events. These days I’m learning about Terraform. That’s why I passed by the booth to say hi.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://mongodb.com/" target="_blank" rel="noopener noreferrer">MongoDB&lt;/a>: MongoDB is on my list of technologies I would like to learn more about. The resources they shared with me were so helpful for starting my learning journey.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://n3xgen.io/" target="_blank" rel="noopener noreferrer">Nextgen.io&lt;/a>: Nexgen.io is a platform that provides support in the following areas:&lt;/p>
&lt;ul>
&lt;li>Transform the traditional monolithic application to micro services&lt;/li>
&lt;li>Provide out-of-the box DevOps features for any development project&lt;/li>
&lt;li>Provide Solid and highly scalable container based platform&lt;/li>
&lt;li>Provide built-in application integration capability with efficient data mapping tool&lt;/li>
&lt;li>Provide out-of-the box B2B capability.&lt;/li>
&lt;li>Managed Services with free upgrade&lt;/li>
&lt;/ul>
&lt;p>I was interested in knowing more about the DevOps solutions they provide and what I learned is that through the platform you can get help on setting up the CI/CD pipelines of your project.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://aws.amazon.com/developer/community/community-builders/" target="_blank" rel="noopener noreferrer">AWS Community Builders&lt;/a>: Being a member of the GitLab Heroes, GitKraken Ambassadors and HashiCorp Ambassadors programs let me learn more about DevOps and know some tools I use regularly, as well as improving my writing and public speaking skills. Knowing that there’s a AWS Community Builders program where you can be recognized for your contributions to the community, and know more about AWS, the tools and services you have access to when registering on the platform, is an opportunity for anyone looking to be an expert on AWS and expand their network. Applications are not open right now.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Not only was so informative to visit the booths, but allowed me to introduce myself as a Technical Evangelist and be aware of what people know about Percona.&lt;/p>
&lt;h2 id="attending-talks">Attending Talks&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_1c75b5b05a472c20.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_e5aa499fabee2bd3.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_a8abb90dc132bd09.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_a8abb90dc132bd09.webp" alt="AWS Summit Mexico City - Kevin Miller" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>These are the talks I attended during AWS Summit Mexico City:&lt;/p>
&lt;ul>
&lt;li>Keynote - Kevin Miller / AWS VP - Simple Storage Service&lt;/li>
&lt;li>Should I use Serverles? Myths and realities for developers (Spanish) - David Victoria / Emite - Director of Operations&lt;/li>
&lt;li>Learn about AWS Global Infrastructure extended to the border - Leonardo Solano / AWS Senior Hybrid Cloud Solutions Architect&lt;/li>
&lt;li>How to accelerate containers creation process on AWS with AWS App2Container (A2C) - Oscar Ramírez Vital / AWS Solutions Architect&lt;/li>
&lt;li>Accelerating IT modernization in government agencies - Rosendo Martinez, José Luis Vallín&lt;/li>
&lt;li>Introduction to security in the cloud with IAM - Uriel Enrique Arellano / Cloud Engineer - Bootcamp Institute&lt;/li>
&lt;li>Modernization of education at various levels - Alex Luna / AWS Sr. Solutions Architect, Juan Manuel Zenil / Escuela Bancaria y Comercial - CIO&lt;/li>
&lt;/ul>
&lt;p>During the past weeks I’ve been using AWS, had to remember how to create and launch an EC2 instance, I’m learning how to create Kubernetes clusters (with no previous knowledge on k8s) on Amazon Elastic Kubernetes Service, I’ve had to read about AWS Identity and Access Management (IAM), and I’m also learning about eksctl and Terraform.&lt;/p>
&lt;p>What I got after attending these talks is having a better understanding of the services and tools available on AWS, knowing about the global infrastructure of the platform, learning about what Serverless is and getting an overview of IAM.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_8971e2b1d2a2f3f1.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_7a902416734e3828.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_c51473ab7445eddd.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_c51473ab7445eddd.webp" alt="AWS Summit Mexico City - Global Infrastructure" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Containerizing applications is one of the topics I’ve been learning about, but never heard of AWS &lt;a href="https://aws.amazon.com/app2container/" target="_blank" rel="noopener noreferrer">App2Container&lt;/a>, that is a command line tool for containerizing Java and .NET applications. While I don’t use any of those technologies, it was interesting to know this tool.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_5e80ef0b9d2fdb4c.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_bfcc3e849b0c2bc4.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_8ab55710fcec9f3.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_8ab55710fcec9f3.webp" alt="AWS Summit Mexico City - Nu" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>It was also good to hear testimonials of companies and government agencies on how they use AWS, including &lt;a href="https://nubank.com.br/en/" target="_blank" rel="noopener noreferrer">Nu&lt;/a>, &lt;a href="https://www.contpaqi.com/" target="_blank" rel="noopener noreferrer">CONTPAQi&lt;/a> and the government of &lt;a href="https://municipiodequeretaro.gob.mx/" target="_blank" rel="noopener noreferrer">Querétaro&lt;/a> who shared their experience.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_955c2e6a75fd8f3a.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_6e2725ad19067e77.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_b7a3069ae7503446.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_b7a3069ae7503446.webp" alt="AWS Summit Mexico City - CONTPAQi" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Back in March, it was &lt;a href="https://aws.amazon.com/blogs/publicsector/aws-announces-local-zones-latin-america/" target="_blank" rel="noopener noreferrer">announced&lt;/a> that new AWS Local Zones would be launched across Latin America. The new locations included Bogotá, Colombia; Buenos Aires, Argentina; Lima, Peru; Queretaro, Mexico; Rio de Janeiro, Brazil; and Santiago, Chile. This was one of the announcements made in the talks presented by AWS.&lt;/p>
&lt;p>While the recordings are not available on &lt;a href="https://www.youtube.com/c/amazonwebservices" target="_blank" rel="noopener noreferrer">Amazon Web Services YouTube channel&lt;/a>, here’s a list of videos I recommend to watch:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=bmAhMewz_pE" target="_blank" rel="noopener noreferrer">History of success - Municipality of Querétaro (Spanish)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=UuRX2gK0IYw" target="_blank" rel="noopener noreferrer">AWS Global Infrastructure Explainer Video&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=YMj33ToS8cI" target="_blank" rel="noopener noreferrer">AWS re:Inforce 2022 - AWS Identity and Access Management (IAM) deep dive (IAM301)&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="networking">Networking&lt;/h2>
&lt;p>Attending AWS Summit Mexico City was also an opportunity for meeting people I hadn’t seen in a long time, like a friend that works at Accenture who I saw the last time back in 2013 at the Firefox OS launch event in Mexico City, and another friend that is also a GitLab Hero who now works at Dynatrace, and I never met in person before.&lt;/p>
&lt;p>Having conversations with sponsors and some attendees gave me an overview of what people know about Percona. Most of the people I talked to never heard of Percona before and some were interested to know more about what we do.&lt;/p>
&lt;p>And I also was able to meet and spend time with &lt;a href="https://twitter.com/EdithPuclla" target="_blank" rel="noopener noreferrer">Edith&lt;/a> and &lt;a href="https://twitter.com/dberkholz" target="_blank" rel="noopener noreferrer">Donnie&lt;/a>, Perconians who were also attending the event.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_367911d9cee6c4fa.webp 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_33cc8dc1da6a60a0.webp 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_f661433bb504a6d1.webp 1400w" src="https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_f661433bb504a6d1.webp" alt="AWS Summit Mexico City - Perconians" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="how-was-the-whole-experience">How Was the Whole Experience?&lt;/h2>
&lt;p>The good:&lt;/p>
&lt;ul>
&lt;li>Getting an overview of AWS: Services and tools, and global infrastructure&lt;/li>
&lt;li>Understanding serverless&lt;/li>
&lt;li>Learning about AWS Identity and Access Management (IAM)&lt;/li>
&lt;li>Hearing testimonials of how AWS is being used&lt;/li>
&lt;li>Simultaneous sessions&lt;/li>
&lt;li>Live translation for talks in English&lt;/li>
&lt;/ul>
&lt;p>Expectations for future AWS events:&lt;/p>
&lt;ul>
&lt;li>More technical sessions, especially workshops.&lt;/li>
&lt;li>Different networking and after-event activities (by organizers and sponsors, and local AWS communities).&lt;/li>
&lt;li>A venue with Internet connection&lt;/li>
&lt;/ul>
&lt;p>In general, attending AWS Summit Mexico City or any AWS event in the future is something I would recommend to anyone who is starting to use AWS or already have some experience, as this is a place to learn from experts and practitioners, get a better understanding of important concepts, an overview of the platform and the services and tools available, as well as expand the network of contacts and meet local community members, and AWS users.&lt;/p>
&lt;h2 id="after-the-event">After the Event&lt;/h2>
&lt;p>After two days at AWS Summit Mexico City, Edith and I traveled to Querétaro, a city located two and a half hours from Mexico City, for meeting other Perconians. Once there we gathered together for having breakfast and later worked at a local coworking space. We met Mauricio, Eduardo, and David from the Managed Services team.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="4000" height="2252" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/perconians-queretaro_hu_9c96fa2904adfaf1.webp 768w, https://percona.community/blog/2022/10/perconians-queretaro_hu_7fa2cd87e30e818c.webp 480w, https://percona.community/blog/2022/10/perconians-queretaro_hu_a931bdb65cd477e3.webp 1400w" src="https://percona.community/blog/2022/10/perconians-queretaro_hu_a931bdb65cd477e3.webp" alt="Perconians at Querétaro" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="resources">Resources&lt;/h2>
&lt;p>Some good resources that were shared during the event:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://explore.skillbuilder.aws/learn" target="_blank" rel="noopener noreferrer">AWS Skill Builder&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.aws.amazon.com/whitepapers/latest/aws-overview" target="_blank" rel="noopener noreferrer">Overview of Amazon Web Services&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/architecture/well-architected" target="_blank" rel="noopener noreferrer">AWS Well Architected&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://serverlessland.com/" target="_blank" rel="noopener noreferrer">Serverless Land&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://university.mongodb.com/" target="_blank" rel="noopener noreferrer">MongoDB University&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Mario García</author><category>AWS</category><category>Conference</category><media:thumbnail url="https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_d86c1eb6d16bd83d.jpg"/><media:content url="https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_aba29feb32849439.jpg" medium="image"/></item><item><title>MySQL: Tracing a single query with PERFORMANCE_SCHEMA</title><link>https://percona.community/blog/2022/10/18/mysql-tracing-a-single-query-with-performance_schema/</link><guid>https://percona.community/blog/2022/10/18/mysql-tracing-a-single-query-with-performance_schema/</guid><pubDate>Tue, 18 Oct 2022 00:00:00 UTC</pubDate><description>My task is to collect performance data about a single query, using PERFORMANCE_SCHEMA (P_S for short) in MySQL, to ship it elsewhere for integration with other data.</description><content:encoded>&lt;p>My task is to collect performance data about a single query, using &lt;code>PERFORMANCE_SCHEMA&lt;/code> (P_S for short) in MySQL, to ship it elsewhere for integration with other data.&lt;/p>
&lt;p>In a grander scheme of things, I will need to define what performance data from a query I am actually interested in.
I will also need to find a way to attribute the query (as seen on the server) to a point in the codebase of the client, which is not always easy when an ORM or other SQL generator is being used.
And finally I will need to find a way to view the query execution in the context of the client code execution, because the data access is only a part of the system performance.&lt;/p>
&lt;p>But this is about query execution in the server, and the instrumentation available to me in MySQL 8, at least to get things started.
So we take the tour of performance schema, and then run one example query (a simple join) and see what we can find out about this query.&lt;/p>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/2021/09/15/mysql-tracing-a-single-query-with-performanceschema.html" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the &lt;a href="https://percona.community/contributors/koehntopp/">author&lt;/a>.&lt;/em>&lt;/p>
&lt;h1 id="performance-schema-the-10000-m-view">Performance Schema, the 10.000 m view&lt;/h1>
&lt;p>&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html" target="_blank" rel="noopener noreferrer">The Manual&lt;/a> has a major chapter that covers P_S in details.
The original idea of P_S is to have a bunch of preallocated memory areas without locks, presented to the database itself as tables.&lt;/p>
&lt;p>P_S is unusual in the way that P_S “tables” are never locked while you work with them.
That means the values in a “table” can change while you read them.
That is important - if you for example calculate percentages, they may not add up to 100%.
If you &lt;code>ORDER BY&lt;/code>, the sort may or may not be stable.&lt;/p>
&lt;p>These are good properties: P_S will not freeze the server, and you won’t kill the server by working with P_S tables.&lt;/p>
&lt;p>It is a good idea to make a copy of P_S tables while you work with them, by turning off subquery merging with &lt;code>select /*+ NO_MERGE(t) */ &lt;/code>, and then materializing P_S tables in subqueries.&lt;/p>
&lt;p>Originally, P_S also had no secondary indexes, so joining P_S tables against other P_S tables did not work efficiently.
That was probably a good idea, because joining against a table that is changing while you execute the join is probably generating random results anyway.
But because it is so common, and because MySQL itself does this now internally in &lt;code>sys.*&lt;/code>, secondary indexes to join efficiently now exist.
That does not make the joins more correct, but at least you get the result faster.&lt;/p>
&lt;p>I wrote about all this &lt;a href="https://blog.koehntopp.info/2020/12/01/not-joining-on-performance-schema.html" target="_blank" rel="noopener noreferrer">in an earlier article&lt;/a>.&lt;/p>
&lt;h2 id="instruments-objects-actors-threads-and-consumers">Instruments, Objects, Actors, Threads and Consumers&lt;/h2>
&lt;p>The data P_S collects is centered around three major things:&lt;/p>
&lt;ul>
&lt;li>Time consumed. In database servers, that is mostly wait time - waiting on I/O or locks.&lt;/li>
&lt;li>Data transferred. In database servers, that is mostly pages read or written. In a way, this related to I/O wait.&lt;/li>
&lt;li>Memory used. In database servers, that is buffers allocated - how large, and how often, and peak usage.&lt;/li>
&lt;/ul>
&lt;p>P_S collects this in the form of “events” (and takes care to note that P_S events are not binlog events or other any events).
The collection points are in the database server code, which is instrumented, so the collectors are &lt;em>instruments&lt;/em>.&lt;/p>
&lt;p>The thing that the server code works on may be of a certain kind, for example a table or another &lt;em>object&lt;/em> in the server, but have a variable identity (that would be different tables, with different names).
Instruments can be filtered by using object names.&lt;/p>
&lt;p>The activity done in the server is done on behalf of a database user, in the form of &lt;em>user@host&lt;/em> or, new in MySQL 8, using roles.
The entity on which behalf the server is working on is called the &lt;em>actor&lt;/em>.&lt;/p>
&lt;p>The activity done in the server is also done in the context of a &lt;em>thread&lt;/em>, some of which are background threads, while the majority in a busy server are usually connection threads.&lt;/p>
&lt;p>And finally, the data collected is put into the in-memory tables of P_S.
These come in various groups, and are called &lt;em>consumers&lt;/em>.&lt;/p>
&lt;img src="https://blog.koehntopp.info/uploads/2021/09/performance_schema_filtering.png" />
&lt;p>&lt;em>Data is collected from objects using instruments. Instruments can be turned on and off. Their collected data is then filtered by Objects, Actors and Threads, and finally dropped into consumers. Many consumers are aggregates, some collect information specific to one query execution.&lt;/em>&lt;/p>
&lt;p>For each of these things there is a &lt;code>setup_...&lt;/code> table that controls how event data is collected by the instrumentation, filtered and the consumed in result tables.
In parallel, object identities are collected in &lt;code>..._instances&lt;/code> tables, which are needed to resolve object identities.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tables&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">like&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"setup_%"&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Tables_in_performance_schema&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">setup_&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setup_actors&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setup_consumers&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setup_instruments&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setup_objects&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">setup_threads&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tables&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">like&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"%_instances"&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">--------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Tables_in_performance_schema&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">_instances&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">--------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cond_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">file_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mutex_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">prepared_statements_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rwlock_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket_instances&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">--------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">6&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket_instances&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+-----------------------+-----------+-----------+-----------+-------+--------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">EVENT_NAME&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECT_INSTANCE_BEGIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">THREAD_ID&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SOCKET_ID&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PORT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">STATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+-----------------------+-----------+-----------+-----------+-------+--------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mysqlx&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">tcpip_socket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">106328376&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">44&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">21&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">18025&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ACTIVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mysqlx&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">unix_socket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">106328688&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">44&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">22&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ACTIVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">server_tcpip_socket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">106329000&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">26&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">127&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ACTIVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">server_unix_socket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">106329312&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">28&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ACTIVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">client_connection&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">106330560&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">41&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ACTIVE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">----------------------------------------+-----------------------+-----------+-----------+-----------+-------+--------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="current-history-and-history-long-tables-vs-summaries">Current, History and History Long Tables vs. Summaries&lt;/h2>
&lt;p>P_S collects data in a lot of summary table, which should not interest us that much here.
Our task is to look at the performance data of a single, individual query to better understand what happened when it ran.&lt;/p>
&lt;p>These unaggregated tables are &lt;code>events_transactions&lt;/code>, &lt;code>events_statements&lt;/code>, &lt;code>events_stages&lt;/code> and &lt;code>events_waits&lt;/code>.
For each of them, we have &lt;code>_current&lt;/code>, &lt;code>_history&lt;/code> or &lt;code>_history_long&lt;/code> tables.&lt;/p>
&lt;p>The &lt;code>_current&lt;/code> tables contain one entry for the currently running thread.
The &lt;code>_history&lt;/code> tables contain a configurable number of entries for each thread, for example 10 per thread.
And the &lt;code>_history_long&lt;/code> tables contain a configurable number of entries, shared across all threads, for example 10.000 in total.
As the server continues to execute statements and produce events, old entries are discarded and new entries are added, automatically.
Additionally, each query execution is aggregated along several dimensions in summary tables.
Summary tables state these dimension(s) using &lt;code>by_&lt;dimensionname>&lt;/code>, for example &lt;code>_by_user_by_eventname&lt;/code> or similar.&lt;/p>
&lt;p>In current MySQL, P_S is enabled by default.
But not all instruments and consumers are enabled, because some instrumentation slows query execution down, and some consumers can use a lot of memory.
To be fast and safe, all memory is statically allocated at config change so the memory resource usage of P_S is constant and no allocations are made during execution.&lt;/p>
&lt;p>We can enable all instrumentation completely with this SQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setup_instruments&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENABLED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'YES'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">TIMED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'YES'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">Query&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OK&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">494&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">affected&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">Rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">matched&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1216&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Changed&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">494&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Warnings&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setup_consumers&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENABLED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'YES'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">Query&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OK&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">affected&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">Rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">matched&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Changed&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Warnings&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When we look at one of these tables, for example &lt;code>events_statements_current&lt;/code>, we see a structure like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_current&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">Table&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_current&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">Create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">Table&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">events_statements_current&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">THREAD_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">EVENT_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">END_EVENT_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">EVENT_NAME&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="k">SOURCE&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">64&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">TIMER_START&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">TIMER_END&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">TIMER_WAIT&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">NESTING_EVENT_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">NESTING_EVENT_TYPE&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">enum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'TRANSACTION'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'STATEMENT'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'STAGE'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">'WAIT'&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">NESTING_EVENT_LEVEL&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">STATEMENT_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">unsigned&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">PRIMARY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">KEY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">THREAD_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">EVENT_ID&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENGINE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">PERFORMANCE_SCHEMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CHARSET&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">utf8mb4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLLATE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">utf8mb4_0900_ai_ci&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That is, events are tagged with a &lt;code>THREAD_ID&lt;/code> (which is not a CONNECTION_ID() as seen in processlist), an &lt;code>EVENT_ID/END_EVENT_ID&lt;/code> bracket, various source and timer values and for further dissection, a &lt;code>NESTING_EVENT_ID&lt;/code> and &lt;code>_TYPE&lt;/code>.&lt;/p>
&lt;p>We can translate processlist ids into thread ids using the &lt;code>P_S.THREADS&lt;/code> table, and then use this to limit our view on the &lt;code>events_statements_current&lt;/code> table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">processlist_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">connection_id&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_current&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">88463&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">statement&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">init_net_server_extension&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">94&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">):&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">341&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">04&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_current&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So running this query took 341 Microseconds, or 0.341 ms.
And sources are named after their location in the server sourcecode, &lt;a href="https://github.com/mysql/mysql-server/blob/8.0/sql/conn_handler/init_net_server_extension.cc#L94-L96" target="_blank" rel="noopener noreferrer">filename and line number&lt;/a>.&lt;/p>
&lt;p>Events exist in a hierarchy: wait events nest within stage events, which nest within statement events, which nest within transaction events.
Some nested events refer to their own type, for example, statement events can point to other statement events they are nested in.
Other events refer to their enclosing context in the hierarchy.
Nesting Event ID, Type and Level make this clear.&lt;/p>
&lt;h2 id="instrument-names">Instrument names&lt;/h2>
&lt;p>The manual explains &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/performance-schema-instrument-naming.html" target="_blank" rel="noopener noreferrer">Instrument Names&lt;/a>.&lt;/p>
&lt;p>They have path-like names that group instruments hierarchically, for example &lt;code>wait/io/file/innodb/innodb_data_file&lt;/code>.
This is a &lt;code>wait&lt;/code> event, &lt;code>io&lt;/code> related, specifically &lt;code>file&lt;/code> I/O, more specific &lt;code>innodb&lt;/code> and even more specific &lt;code>innodb_data_file&lt;/code>.
Looking at other fields in the &lt;code>events_waits_history&lt;/code> table, we would see the file name as part of the &lt;code>OBJECT_SCHEMA.OBJECT_NAME&lt;/code> designator for this event.
That means, we can see how long we waited for I/O coming from this specific file or going to the file.&lt;/p>
&lt;p>Further up in the nesting we would see, at the statement level, the actual &lt;code>SQL_TEXT&lt;/code>, and also the number of rows scanned.
That means we can get a rough estimate why this particular statement instance was slow - for example, the plan was good, the number of rows was low, but we see a lot of actual file IO waits, so probably the buffer pool was cold.&lt;/p>
&lt;p>The manual page above discusses the instrument names at length, and it is important to get an overview of what exists and what is measured.
Specifically, for statement level entries the instruments vary during query execution and become more detailed, as they reflect the progress in understanding of the server about the nature of the statement as it is executed.&lt;/p>
&lt;h1 id="an-example-run">An example run&lt;/h1>
&lt;p>In a freshly restarted idle server, we log in to a shell and &lt;code>use world&lt;/code> for the world sample database.
This is a tiny database, but because the server has been just restarted, nothing of it is cached.
We run a simple query:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">world&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">copop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cipop&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Europe'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-----------+------------------------------------+---------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">copop&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cipop&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-----------+------------------------------------+---------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Europe&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Albania&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3401200&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Tirana&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">270000&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Europe&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Yugoslavia&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">10640000&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Beograd&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1204000&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-----------+------------------------------------+---------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">46&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now let’s check what we can find out, using a second session:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">processlist_db&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'world'&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">THREAD_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">one_connection&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FOREGROUND&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_HOST&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">world&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PROCESSLIST_COMMAND&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_TIME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_STATE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESSLIST_INFO&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PARENT_THREAD_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">ROLE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">INSTRUMENTED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">YES&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">HISTORY&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">YES&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">CONNECTION_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Socket&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">THREAD_OS_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">346752&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">RESOURCE_GROUP&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">USR_default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In our case we are only interested in the fact that our &lt;code>thread/sql/one_connection&lt;/code> in the processlist is shown as connection &lt;code>9&lt;/code>, but internally has a thread_id of &lt;code>48&lt;/code>.
The Linux Operating System PID is &lt;code>346752&lt;/code>.&lt;/p>
&lt;p>We can use this to check &lt;code>events_transactions_wait&lt;/code>, and find&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">state&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_type&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_transactions_history&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-----------+-----------------+-----------+------------------+--------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">state&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_type&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-----------+-----------------+-----------+------------------+--------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">179&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COMMITTED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1328&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">496&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">05&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">85&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">STATEMENT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">401&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COMMITTED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1328&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">559&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">99&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">278&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">STATEMENT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5606&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COMMITTED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1328&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">03&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ms&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">STATEMENT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-----------+-----------------+-----------+------------------+--------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Why are there three statement events? We can check:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_history&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">85&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">278&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">85&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">databases&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">278&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tables&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">copop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cipop&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Europe'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The mysql command line client is running from the sandbox with &lt;code>/home/kris/opt/mysql/8.0.25/bin/mysql --defaults-file=/home/kris/sandboxes/msb_8_0_25/my.sandbox.cnf world&lt;/code>.
Autocompletion for names is not disabled.
So on client startup, the client invisibly runs &lt;code>show databases&lt;/code> to learn the names of all databases for autocompletion.
It then enters the &lt;code>world&lt;/code> database as requested and runs &lt;code>show tables&lt;/code> to learn the names of all tables in the &lt;code>world&lt;/code> database.&lt;/p>
&lt;p>Only then we come to the prompt and can paste our query.&lt;/p>
&lt;p>We are only interested in &lt;code>thread_id = 48 AND event_id = 5574&lt;/code>, our query.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_statements_history&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">THREAD_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">EVENT_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">END_EVENT_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6624&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">EVENT_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">statement&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">SOURCE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">init_net_server_extension&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">94&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">TIMER_START&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">61628458852000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">TIMER_END&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">61631769329000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">TIMER_WAIT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3310477000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">LOCK_TIME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">224000000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SQL_TEXT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">copop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cipop&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'Europe'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">DIGEST&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">409&lt;/span>&lt;span class="n">c336982f0d3d45c4b29da77fe83aed12c6043e8ce9771c11ec82ff347e647&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">DIGEST_TEXT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">copop&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">population&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">cipop&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">co&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ci&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">continent&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">CURRENT_SCHEMA&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">world&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECT_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECT_SCHEMA&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECT_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECT_INSTANCE_BEGIN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">MYSQL_ERRNO&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">RETURNED_SQLSTATE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">MESSAGE_TEXT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ERRORS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">WARNINGS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ROWS_AFFECTED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ROWS_SENT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">46&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ROWS_EXAMINED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">92&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">CREATED_TMP_DISK_TABLES&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">CREATED_TMP_TABLES&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SELECT_FULL_JOIN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SELECT_FULL_RANGE_JOIN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SELECT_RANGE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SELECT_RANGE_CHECK&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SELECT_SCAN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SORT_MERGE_PASSES&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SORT_RANGE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SORT_ROWS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SORT_SCAN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NO_INDEX_USED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NO_GOOD_INDEX_USED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NESTING_EVENT_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NESTING_EVENT_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">NESTING_EVENT_LEVEL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">STATEMENT_ID&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">125&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The statement is &lt;code>statement/sql/select&lt;/code>.
It took 3310477000 picoseconds (3.31ms) to run.
The &lt;code>sql_text&lt;/code> is the full text of the statement (up to a cutoff point, in order to manage memory consumption).
The parsed statement is called &lt;code>digest_text&lt;/code> - identifiers are quoted, whitespace is normalized, actual constants are replaced with placeholders, and (not shown here) variable length &lt;code>WHERE ... IN (...)&lt;/code> clauses are shortened with ellipses.
This normalized digest is then hashed and produces an identifier for this group of identically formed statements, the &lt;code>digest&lt;/code>.
We learn about the number of rows looked at, &lt;code>92&lt;/code> and sent, &lt;code>46&lt;/code>.
No special flags indicating specific execution modes were set.&lt;/p>
&lt;p>We can use the &lt;code>event_id&lt;/code> to look even deeper:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_stages_history&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5574&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+--------------------------------------+----------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+--------------------------------------+----------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5610&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">optimizing&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_optimizer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">270&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">88&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5611&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">statistics&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_optimizer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">534&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">703&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">58&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!!&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5710&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">preparing&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_optimizer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">618&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">93&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5712&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">executing&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_union&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1126&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">26&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ms&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&lt;-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!!&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6593&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_select&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">586&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">86&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6594&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_parse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">4542&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">93&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6596&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">waiting&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">handler&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">commit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1594&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">05&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">closing&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tables&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_parse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">4593&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">14&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">02&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6621&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">freeing&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_parse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">5042&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">29&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6623&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stage&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">cleaning&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">up&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sql_parse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">cc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">2252&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">17&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+--------------------------------------+----------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>These are the various execution stages of our statement - we select by &lt;code>thread_id&lt;/code> and with the &lt;code>event_id&lt;/code> of the statement, &lt;code>5574&lt;/code> as a &lt;code>nesting_id&lt;/code>, ordered by &lt;code>event_id&lt;/code>.
Time was consumed by the &lt;code>stage/sql/statistics&lt;/code> phase, looking up table stats for a good execution plan, and then by the actual query execution in &lt;code>stage/sql/executing&lt;/code>.
The former took 0.7ms (703.58us), the latter 2.26ms.&lt;/p>
&lt;p>We are interested in what took so long, specifically, so we look into waits for event_ids 5611 and 5712 - finding nothing, and also nothing particularly time-consuming:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timer_wait&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_type&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">operation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_waits_history&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+------------------------------------------+-----------+--------------------+------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_type&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">nesting_event_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">operation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+------------------------------------------+-----------+--------------------+------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6614&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">56&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">26&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6615&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">64&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6616&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6617&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">46&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">88&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6618&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">43&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">36&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6619&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">trx_mutex&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">45&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6620&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">LOCK_table_cache&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">66&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6622&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">client_connection&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">18&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">91&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6621&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">send&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6624&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">synch&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">mutex&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">THD&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="n">LOCK_thd_query&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">124&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">23&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ns&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">STAGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6623&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">lock&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6626&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">client_connection&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WAIT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">6625&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">recv&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+----------+------------------------------------------+-----------+--------------------+------------------+-----------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can see I/O in a global summary, and the timings make sense in the context of the experiment:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">object_type&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">object_schema&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">object_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">count_star&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_timer_read&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="k">read&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_timer_write&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="k">write&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_timer_fetch&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="k">fetch&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">table_io_waits_summary_by_table&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">object_schema&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'world'&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="k">G&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">object_type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">object_schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">world&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">object_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">count_star&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">46&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">read&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">569&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">17&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">write&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ps&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">fetch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">569&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">17&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">row&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">***************************&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">object_type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">object_schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">world&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">object_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">count_star&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">47&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">read&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">703&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">write&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ps&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">fetch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">703&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">us&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But why are the I/O times not visible to us?
That’s a bit unclear.
My theory was that the reads happen asynchronously by some background thread.
But a quick query shows no time spend on reader threads.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">type&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_waits_summary_by_thread_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">max_timer_wait&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'wait/io/file/innodb/innodb_data_file'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+---------------------------------------------+------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">type&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+---------------------------------------------+------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io_write_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io_write_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">11&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io_write_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">io_write_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">13&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">page_flush_coordinator_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">33&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">clone_gtid_thread&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">47&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">one_connection&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FOREGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">one_connection&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FOREGROUND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+---------------------------------------------+------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We seem to be unable to attribute time spent loading data from disk to a specific thread, and we seem to be unable to account for the runtime of certain stages by looking at waits.
That’s unexpected.&lt;/p>
&lt;h1 id="memory-only-as-summary">Memory only as summary&lt;/h1>
&lt;p>Diverse &lt;code>memory_%&lt;/code> tables exist to track memory usage in the server.
All of these tables are summary tables, there are no memory events tables that could trace memory usage per query.
That might be okay.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">show&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tables&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">like&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'%memory%'&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Tables_in_performance_schema&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_by_account_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_by_host_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_by_thread_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_by_user_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_global_by_event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------------------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can do interesting things with stuff such as &lt;code>memory_summary_by_thread_by_event_name&lt;/code>, at least on our mostly idle server.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mysql&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">localhost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8025&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">{&lt;/span>&lt;span class="n">msandbox&lt;/span>&lt;span class="err">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">count_alloc&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sum_number_of_bytes_alloc&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">high_number_of_bytes_used&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory_summary_by_thread_by_event_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HIGH_NUMBER_OF_BYTES_USED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">>&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">->&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">order&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HIGH_NUMBER_OF_BYTES_USED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">desc&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">limit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-------------+---------------------------+---------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">count_alloc&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sum_number_of_bytes_alloc&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">high_number_of_bytes_used&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-------------+---------------------------+---------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">THD&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="n">main_mem_root&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">22&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1181008&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">613544&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">226&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1059368&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">250032&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">dd&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="n">objects&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">205&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">47432&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">44648&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">ha_innodb&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">26&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">35784&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">35784&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">memory&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">innodb&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">fil0fil&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">65600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">32800&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="c1">-----------+-------------------------------+-------------+---------------------------+---------------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">rows&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h1 id="no-explain">No EXPLAIN&lt;/h1>
&lt;p>Another thing that would be useful to collect from P_S is the actual execution plan of a query.
But while we can explain a lot of statements by running &lt;code>EXPLAIN &lt;stmt>&lt;/code>, and while we can &lt;code>EXPLAIN FOR CONNECT ...&lt;/code>, the former is not the recorded execution plan, and the latter only works while the query is running.
It’s the actual execution plan while the query executes, but it is not recorded.&lt;/p>
&lt;h1 id="summary">Summary&lt;/h1>
&lt;p>A lot of information about query execution can be gathered from P_S.
The query execution can be broken down in statements, stages and waits.
Specifically, statements collect a lot of interesting quality flags.
Stages can collect percentages of completion for long-running queries and give a general feel about where in the query execution time is spent.
Waits should be able to attribute time to individual operations in the database server, but specifically for file I/O this seems to be more complicated, and I have not been able to solve it.&lt;/p>
&lt;p>We can see waits for I/O summary tables, and we can see a lot of other statistical information in other summary tables.
We can also use additional tables not covered here for debugging (for example &lt;code>DATA_LOCKS&lt;/code> for locking behavior).&lt;/p>
&lt;p>Memory instrumentation is interesting, but at this stage it is unclear to me if it is sufficient.&lt;/p>
&lt;p>It seems to be really hard to record execution plans together with statements.&lt;/p>
&lt;p>More experimentation with more complicated queries is necessary to see if it is possible to see things like sorting, temp files and similar operations, and attribute time to these operations.&lt;/p>
&lt;p>The number of queries on P_S necessary to extract information about a single query is staggering, a 10:1 ratio.
At least filters exist and are on by default, so that I do not have to hear my monitoring noise in my monitoring.
That is good.&lt;/p>
&lt;p>I could really use a single large JSON blob containing the entire package with performance data for a query, at once - one query to trace one query.
That is, the information from transaction, statement, stages, waits, the execution plan and the memory consumption for a given transaction or statement, in one go.&lt;/p>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/2021/09/15/mysql-tracing-a-single-query-with-performanceschema.html" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the &lt;a href="https://percona.community/contributors/koehntopp/">author&lt;/a>.&lt;/em>&lt;/p></content:encoded><author>Kristian Köhntopp</author><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2022/10/mysql-tracing-a-single-query_hu_b2d226be793a1ee7.jpg"/><media:content url="https://percona.community/blog/2022/10/mysql-tracing-a-single-query_hu_bd5f0967e8de6c92.jpg" medium="image"/></item><item><title>What is Open Source and why should you care</title><link>https://percona.community/blog/2022/10/14/what-is-open-source-and-why-should-you-care/</link><guid>https://percona.community/blog/2022/10/14/what-is-open-source-and-why-should-you-care/</guid><pubDate>Fri, 14 Oct 2022 00:00:00 UTC</pubDate><description>The term Open Source Software reminds me of Abraham Lincoln’s widely accepted definition of Democracy. Lincoln said, “Democracy is the government of the people, by the people, and for the people”. Similarly, Open Source is software of the community, by the community, and for the community.</description><content:encoded>&lt;p>The term Open Source Software reminds me of &lt;a href="https://en.wikipedia.org/wiki/Abraham_Lincoln#Gettysburg_Address_%281863%29" target="_blank" rel="noopener noreferrer">Abraham Lincoln’s&lt;/a> widely accepted definition of Democracy. Lincoln said, “Democracy is the government of the people, by the people, and for the people”. Similarly, Open Source is &lt;strong>software&lt;/strong> of the &lt;strong>community&lt;/strong>, by the &lt;strong>community&lt;/strong>, and for the &lt;strong>community&lt;/strong>.&lt;/p>
&lt;p>Formally, Open Source refers to &lt;strong>projects&lt;/strong> or &lt;strong>programs&lt;/strong> whose source code can be modified, shared, and/or commercialized by the public/community at will.
A &lt;a href="https://choosealicense.com/" target="_blank" rel="noopener noreferrer">&lt;strong>license&lt;/strong>&lt;/a> that is attached to an Open Source project determines the extent to which the project can be consumed, modified, or utilized.&lt;/p>
&lt;p>Open Source is everywhere, the browser you are using to view this page is likely an Open Sourced one. Android OS, Linux Kernel, and JavaScript are examples of OS projects that people like you and I improve daily. As a developer, technical writer, or designer; Open Source opens the doors to many opportunities; jobs, networking, communication skills, etc.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note&lt;/strong>: The term Open Source in this article will mostly refer to Open Source Software and Open Source in general.&lt;/p>&lt;/blockquote>
&lt;h2 id="table-of-contents">&lt;a href="#table-of-contents">Table of contents&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="#benefits-of-open-source">Benefits of open source&lt;/a>&lt;/li>
&lt;li>&lt;a href="#getting-started-with-open-source">Getting started with Open Source&lt;/a>&lt;/li>
&lt;li>&lt;a href="#clearing-up-some-misconceptions">Clearing up some misconceptions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#where-to-find-open-source-projects">Where to find Open Source projects&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#finding-projects-to-contribute-to">Finding projects to contribute to&lt;/a>&lt;/li>
&lt;li>&lt;a href="#knowing-where-to-make-changes-or-contributions-to-a-project">Knowing where to make changes or contributions in a project&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#what-kind-of-contributions-are-legit">What kind of contributions are legit&lt;/a>&lt;/li>
&lt;li>&lt;a href="#some-interesting-github-projects-you-can-contribute-to">Some Interesting GitHub projects you can contribute to&lt;/a>&lt;/li>
&lt;li>&lt;a href="#joining-the-open-source-community">Joining the Open Source community&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#notable-open-source-advocates">Notable Open Source Advocates&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#bonus-hacktoberfest">Bonus: Hacktoberfest&lt;/a>&lt;/li>
&lt;li>&lt;a href="#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="benefits-of-open-source">Benefits of open source&lt;/h2>
&lt;p>Being a member of the Open Source Community brings many benefits including job opportunities, sponsorships, networking, improved communication skills, software integrity, and much more. Job recruiters watch out for people who give back to the community. Contributing to OS will boost your chances of getting jobs because it shows that you are willing to foster new relationships and work with others. Remotely interacting with new people also improves your ability to communicate ideas effectively when working on projects.&lt;/p>
&lt;p>In the OS community, you are free to break things and make honest mistakes. Others will fix them and ensure that the software is working correctly.
With Open Source, even when you abandon a project. The community will keep it alive. This saves you a great deal of time to work on other projects. Some major benefits of contributing to Open Source include:&lt;/p>
&lt;ul>
&lt;li>Getting free Swag.&lt;/li>
&lt;li>Networking and meeting new people.&lt;/li>
&lt;li>It makes you a better coder/writer - People will correct you whenever you break stuff or find errors in your work.&lt;/li>
&lt;/ul>
&lt;h2 id="getting-started-with-open-source">Getting started with Open Source&lt;/h2>
&lt;p>Are you new to the Open Source Community? If your answer is yes, then you are in the right place. Even if your answer is No, there are a few tips that you can still take away from this post.&lt;/p>
&lt;h2 id="clearing-up-some-misconceptions">Clearing up some misconceptions&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Ability to code is necessary for Open Source&lt;/strong>: Open Source is not limited to software, there are many low code/ no-code OS projects that you can find on GitHub today.&lt;/li>
&lt;li>&lt;strong>High technical skill is required to start contributing&lt;/strong>: This is not true at all, simple changes like fixing typos, grammatical corrections, and spelling errors are all welcome in the OS community as long as it adds value or improvement.&lt;/li>
&lt;/ul>
&lt;h2 id="where-to-find-open-source-projects">Where to find Open Source projects&lt;/h2>
&lt;p>If you are looking for Open Source Projects to contribute to, look no further than GitHub - the defacto home of Open Source projects.
GitHub is home to millions of Open Source projects. There are lots of projects that can align with your specialty if you know how to look properly.&lt;/p>
&lt;p>Finding Open Source projects to contribute to can be difficult, especially for newcomers. One reason is that they are not looking in the right places. When it comes to contributing to Open Source, newcomers are often faced with two problems:&lt;/p>
&lt;ul>
&lt;li>Finding projects to contribute to&lt;/li>
&lt;li>Knowing where to make changes or contributions to a project&lt;/li>
&lt;/ul>
&lt;h3 id="finding-projects-to-contribute-to">Finding projects to contribute to&lt;/h3>
&lt;ol>
&lt;li>Go to &lt;a href="https://github.com" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>&lt;/li>
&lt;li>Click issues in the navigation panel&lt;/li>
&lt;li>Filter the results by entering a keyword into the search bar e.g good first issue, help-wanted, JavaScript, technical writing, documentation, React, etc&lt;/li>
&lt;li>Search for something specific within your domain such as react, documentation, design, etc &lt;strong>good first issue&lt;/strong> and &lt;strong>first-timers only&lt;/strong> are labels for issues that are appropriate for newcomers to work on. Try these first if you are just starting out.&lt;/li>
&lt;li>Find an issue you would like to work on. If it hasn’t been assigned to someone else, ask the maintainers if you can work on the issue and create a PR for it.&lt;/li>
&lt;li>Many big projects often have abandoned or incomplete issues. You can find these by searching for “todo” or looking through much older issues.&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;strong>Note&lt;/strong>: Alternatively, you can visit any of the websites below to find issues without going through the hassle of following the steps above. The following websites are tailor-made for finding issues easily:&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>&lt;a href="https://up-for-grabs.net" target="_blank" rel="noopener noreferrer">up-for-grabs.net&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.firsttimersonly.com/" target="_blank" rel="noopener noreferrer">firstTimersOnly&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://goodfirstissues.com/" target="_blank" rel="noopener noreferrer">goodFirstIssues&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://codetriage.com" target="_blank" rel="noopener noreferrer">codetriage.com&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://goodfirstissue.dev/" target="_blank" rel="noopener noreferrer">goodfirstissue.dev&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>If you are not familiar with Open Source etiquette, then I suggest that you read these briefly:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://opensource.guide/how-to-contribute/#finding-a-project-to-contribute-to" target="_blank" rel="noopener noreferrer">Open Source Guide&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://developer.mozilla.org/en-US/docs/MDN/Contribute/Open_source_etiquette" target="_blank" rel="noopener noreferrer">Open Source Etiquette&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="knowing-where-to-make-changes-or-contributions-to-a-project">Knowing where to make changes or contributions to a project&lt;/h3>
&lt;p>Believe it or not, knowing how to find bugs, errors, mistakes, etc in files in a project is also a skill. If you go into a project’s repo, you’ll likely spot an issue somewhere if you look close enough. some of the most common issues you can find in repos include:&lt;/p>
&lt;ul>
&lt;li>Broken links in websites, project documentation, etc&lt;/li>
&lt;li>Grammatical and Spelling errors&lt;/li>
&lt;li>Lacklustre designs&lt;/li>
&lt;li>Lack of translation, a11y, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" target="_blank" rel="noopener noreferrer">ARIA&lt;/a> etc.&lt;/li>
&lt;li>Absence of comprehensive documentation&lt;/li>
&lt;li>Absence of issue/PR templates in a repo&lt;/li>
&lt;li>Lack of contribution guidelines&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Note&lt;/strong>: Always make sure you go through the README.md and CONTRIBUTING.md files of a repository before creating Pull Requests. This will ensure that you adhere to a project’s guidelines for making contributions. Sometimes valuable PRs are rejected because contributor’s did not follow a Project’s rules. This could be something as trivial as not giving your PR an appropriate title or description. So be wary of this&lt;/p>&lt;/blockquote>
&lt;p>These are just a few off the top of my head. There are many ways in which you can find issues with a project. Ensure that you do not open irrelevant or low-quality issues. Also, bear these in mind before opening issues.&lt;/p>
&lt;ol>
&lt;li>A good way to know if your issue is a quality one is to ask yourself , “how would this change or suggestion help the users and maintainers of this project.” If your answer does not sound credible to you, do not open it.&lt;/li>
&lt;li>Your issue should tell the maintainers of a project the relevance of your suggestion&lt;/li>
&lt;/ol>
&lt;h2 id="what-kind-of-contributions-are-legit">What kind of contributions are legit?&lt;/h2>
&lt;p>Contributions to Open Source are not limited to Pull Requests only. Raising Issues, making Code Reviews, Financial Contributions, etc. are other ways of contributing to Open Source. Until recently, non-code stuff like blog posts, Figma designs, etc was not popular in the Open Source community. Things are beginning to change, however. This year’s &lt;a href="https://hacktoberfest.com" target="_blank" rel="noopener noreferrer">Hacktoberfest&lt;/a> which is accepting low-code and non-code contributions for the first time after 8 years is a good example of this new trend.&lt;/p>
&lt;h2 id="some-interesting-github-projects-you-can-contribute-to">Some Interesting GitHub projects you can contribute to&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://github.com/percona" target="_blank" rel="noopener noreferrer">Percona&lt;/a> - Percona is an Open Source platform for monitoring, securing and optimizing database environments (MySQL, PostgreSQL, MongoDB etc) on any infrastructure.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/EddieHubCommunity/LinkFree" target="_blank" rel="noopener noreferrer">LinkFree&lt;/a> - Free Open Source alternative to Linktree.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/ykdojo/defaang" target="_blank" rel="noopener noreferrer">defaang&lt;/a> - Free Open Source alternative to LeetCode.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/Dun-sin/Code-Magic" target="_blank" rel="noopener noreferrer">Code-Magic&lt;/a> - A website for generating performant CSS with GUI. Plus it’s purely based on TypeScript, CSS, and HTML (No frameworks involved).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/freeCodeCamp/Developer_Quiz_Site" target="_blank" rel="noopener noreferrer">FreeCodeCamp Quiz Site&lt;/a> - You can add new quizzes to the site by following the instructions in the repo.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/Njong392/Abbreve" target="_blank" rel="noopener noreferrer">Abbreve&lt;/a> - A website for quickly checking the meaning of common abbreviations and slang used for communicating over social media.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="joining-the-open-source-community">Joining the Open Source community&lt;/h2>
&lt;p>Community, community, community…
We keep mentioning community in the world of Open Source. This is because Open Source would not exist without the amazing group of individuals who are constantly working hard to make Open Source projects free and accessible to people like you and me.&lt;/p>
&lt;p>Below is a list of arguably the most popular Open Source communities on Twitter (that I know of).&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://discord.gg/freecodecamp-org-official-fi-fo-692816967895220344" target="_blank" rel="noopener noreferrer">FreeCodeCamp&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="http://discord.eddiehub.org/" target="_blank" rel="noopener noreferrer">EddieHub&lt;/a> Everything Open Source (contributions, hackathons, first timers, etc).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://discord.gg/4c-784142072763383858" target="_blank" rel="noopener noreferrer">4C&lt;/a> A large OS community for OS projects and networking.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://discord.gg/nNtVfKddDD" target="_blank" rel="noopener noreferrer">defaang / dojo clan&lt;/a> YK Dojo’s OS community.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com" target="_blank" rel="noopener noreferrer">GitHub&lt;/a> - The largest Open Source community in the world, all the communities listed above have their repositories hosted on GitHub.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Note&lt;/strong>: You should have a good reason for joining any of these communities. If you want to benefit from these communities, you should engage with the members, interact and find people who are within your domain of interests, and do not spam. Also, ensure that you adhere to the rules of these communities too.&lt;/p>&lt;/blockquote>
&lt;h3 id="notable-open-source-advocates">Notable Open Source Advocates&lt;/h3>
&lt;p>Follow these people on Twitter to get all the latest updates on the happenings in the Open Source community:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://twitter.com/eddiejaoude" target="_blank" rel="noopener noreferrer">Eddie Jaoude&lt;/a> - Eddie is a devoted member of the OS community, he hosts Twitter spaces regularly to help people who are just getting started in Open Source. He has a YouTube channel where he creates content beyond Open Source (freelancing tips, content creation tips, mini-tutorials, etc)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://mobile.twitter.com/dunsinwebdev" target="_blank" rel="noopener noreferrer">Dunsin&lt;/a> - Dunsin is the creator of Code-Magic, which is a website for generating CSS code for different effects through GUI. She’s also an active member of the OS community on Twitter.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://twitter.com/ykdojo" target="_blank" rel="noopener noreferrer">YK Dojo&lt;/a> - YK is also a popular YouTuber and an avid member of the OS community. He often does live-coding Streams on Twitch too&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>These are just a few names in the OS community on Twitter. There are so many more Open Source advocates on Twitter apart from the 3 individuals listed above.&lt;/p>
&lt;h2 id="bonus-hacktoberfest">Bonus: Hacktoberfest&lt;/h2>
&lt;p>Hacktoberfest is a yearly celebration of Open Source and the Open Source community throughout October. Hacktoberfest is organized yearly by Digital Ocean.&lt;/p>
&lt;p>A minimum of 4 accepted Pull Requests is required before the 25th of October to win a Hacktoberfest-themed shirt and sticker. Starting from this year, low-code and/or no-code contributions will also be accepted as valid contributions.&lt;/p>
&lt;p>If you’d like to take part in the next Hacktoberfest, set a reminder for October now to avoid missing out on all the fun.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Imagine a world without Open Source. As you can see, there’s nothing to hate about Open Source. The world of Open Source exposes you to opportunities, meeting new friends, winning swag/money . Without Open Source, the world would certainly not be as advanced as it is today in terms of technology.&lt;/p></content:encoded><author>Teslim Balogun</author><category>open-source</category><category>percona</category><category>github</category><category>beginners</category><media:thumbnail url="https://percona.community/blog/2022/10/open-source-cover_hu_b61db9297ad75df3.jpg"/><media:content url="https://percona.community/blog/2022/10/open-source-cover_hu_17dd52579b3e5495.jpg" medium="image"/></item><item><title>Learning Kubernetes Operators with Percona Operator for MongoDB</title><link>https://percona.community/blog/2022/10/13/learning-kubernetes-operators-with-percona-operator-for-mongodb/</link><guid>https://percona.community/blog/2022/10/13/learning-kubernetes-operators-with-percona-operator-for-mongodb/</guid><pubDate>Thu, 13 Oct 2022 00:00:00 UTC</pubDate><description>One of the topics that have resonated a lot for me since the first KubeCon I attended in 2018 is Kubernetes Operators.</description><content:encoded>&lt;p>One of the topics that have resonated a lot for me since the first KubeCon I attended in 2018 is &lt;strong>Kubernetes Operators&lt;/strong>.&lt;/p>
&lt;p>The concept of Operators was introduced much earlier in 2016 by the CoreOS Linux development team. They were looking for a way to improve automated container management in Kubernetes.&lt;/p>
&lt;h2 id="what-do-we-mean-by-a-kubernetes-operator">What do we mean by a Kubernetes Operator?&lt;/h2>
&lt;p>We use the &lt;a href="https://www.cncf.io/blog/2022/06/15/kubernetes-operators-what-are-they-some-examples/#:~:text=K8s%20Operators%20are%20controllers%20for,Custom%20Resource%20Definitions%20%28CRD%29." target="_blank" rel="noopener noreferrer">definition of CNCF&lt;/a>. The Kubernetes project defines &lt;strong>“Operator”&lt;/strong> simply: &lt;strong>“Operators are software extensions that use custom resources to manage applications and their components“&lt;/strong>.&lt;/p>
&lt;p>This means that among the applications that can be run on Kubernetes, there are applications that still require manual operations to manage them and complete the Kubernetes deployment cycle because Kubernetes itself can’t manage all these manual operations. It is what the Operators take care of, to automate those manual processes of the applications deployed in Kubernetes.&lt;/p>
&lt;p>&lt;strong>How can this be possible?&lt;/strong>&lt;/p>
&lt;p>The Operators use/extend the &lt;strong>Kubernetes API&lt;/strong> (this API has the basics needed for a user to interact with the Kubernetes cluster) and create &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#:~:text=A%20custom%20resource%20is%20an,resources%2C%20making%20Kubernetes%20more%20modular." target="_blank" rel="noopener noreferrer">custom resources&lt;/a> to add new functionality according to the needs of an application to be flexible and scalable.&lt;/p>
&lt;p>Once the creation of the &lt;strong>custom resource&lt;/strong> is finished, it creates objects that can be managed using kubectl, as other default Kubernetes resources are managed, such as Deployments, Pods, etc.&lt;/p>
&lt;p>Here we see the difference between the workflows with and without operators.&lt;/p>
&lt;p>&lt;strong>With Operators&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1840" height="1184" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/with-operators_hu_bc6d7b0574700025.webp 768w, https://percona.community/blog/2022/13/with-operators_hu_d669366b864186c3.webp 480w, https://percona.community/blog/2022/13/with-operators_hu_7e17b0ebf6168c99.webp 1400w" src="https://percona.community/blog/2022/13/with-operators_hu_7e17b0ebf6168c99.webp" alt="With Operators" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Without Operators&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1838" height="808" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/without-operators_hu_fe8c59c3f65526a.webp 768w, https://percona.community/blog/2022/13/without-operators_hu_f15bc02df84bdd27.webp 480w, https://percona.community/blog/2022/13/without-operators_hu_b2a19169d6f41b11.webp 1400w" src="https://percona.community/blog/2022/13/without-operators_hu_b2a19169d6f41b11.webp" alt="Without Operators" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The above illustration is based on a presentation by &lt;a href="https://youtu.be/i9V4oCa5f9I?t=403" target="_blank" rel="noopener noreferrer">Sai Vennam&lt;/a>.&lt;/p>
&lt;p>It is time for an example!
We will see how Percona Operator for MongoDB works.&lt;/p>
&lt;p>Percona Operator for MongoDB contains everything we need to quickly and consistently deploy and scale &lt;a href="https://www.percona.com/software/mongodb/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB instances&lt;/a> into a Kubernetes cluster on-premises or in the cloud.&lt;/p>
&lt;p>You can find Percona Operator for MongoDB officially in:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://artifacthub.io/packages/olm/community-operators/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Artifact Hub&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://operatorhub.io/operator/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Operator Hub&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why does Percona Server for MongoDB (a database) need a Kubernetes Operator?&lt;/strong>&lt;/p>
&lt;p>Kubernetes has been designed for stateless applications. Kubernetes in many cases doesn’t require operators for stateless applications because Kubernetes doesn’t need more automation logic. But stateful applications like databases do need operators because they cannot automate the entire process natively.&lt;/p>
&lt;p>One of the main benefits of operators is the automation of repetitive tasks that are often handled by human operators, eliminating errors in application lifecycle management.&lt;/p>
&lt;h2 id="installing-mongodb-percona-operator-using-gke">Installing MongoDB Percona Operator using GKE&lt;/h2>
&lt;p>This guide shows you how to deploy &lt;strong>Percona Operator for MongoDB&lt;/strong> on &lt;strong>Google Kubernetes Engine (GKE)&lt;/strong>. We use GKE which takes less time to set up Kubernetes in Google Cloud just for the purpose of this demo. This demonstration assumes you have some experience with the platform. For more information on the GKE, see &lt;a href="https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster." target="_blank" rel="noopener noreferrer">Kubernetes Engine Quickstart&lt;/a>&lt;/p>
&lt;p>As prerequisites, we need &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/gke.html#prerequisites" target="_blank" rel="noopener noreferrer">Google Cloud shell and Kubectl&lt;/a>. You can find the installation guides for AWS and AZURE in the &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/#advanced-installation-guides" target="_blank" rel="noopener noreferrer">Percona documentation&lt;/a>. Let´s start!&lt;/p>
&lt;ul>
&lt;li>Creating a GKE cluster with three nodes.&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud container clusters create my-cluster-name --project percona-product --zone us-central1-a --cluster-version 1.23 --machine-type n1-standard-4 --num-nodes&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="994" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/1-operators-gcloud_hu_e549925fb0b60d43.webp 768w, https://percona.community/blog/2022/13/1-operators-gcloud_hu_7fff118209a3d069.webp 480w, https://percona.community/blog/2022/13/1-operators-gcloud_hu_41558bf532ea4cb2.webp 1400w" src="https://percona.community/blog/2022/13/1-operators-gcloud_hu_41558bf532ea4cb2.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Now you should configure the command-line access to your newly created cluster to make kubectl able to use it.&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">gcloud container clusters get-credentials my-cluster-name --zone us-central1-a --project percona-product&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1858" height="250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/2-operators-get-credentials_hu_3792dcaa85ebb4b6.webp 768w, https://percona.community/blog/2022/13/2-operators-get-credentials_hu_86deff2c3bb92c45.webp 480w, https://percona.community/blog/2022/13/2-operators-get-credentials_hu_3b3338ac44a2bf21.webp 1400w" src="https://percona.community/blog/2022/13/2-operators-get-credentials_hu_3b3338ac44a2bf21.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Finally, use your &lt;a href="https://cloud.google.com/iam" target="_blank" rel="noopener noreferrer">Cloud Identity and Access Management [Cloud IAM]&lt;/a> to control access to the cluster. The following command will give you the ability to create Roles and RoleBindings:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user &lt;span class="k">$(&lt;/span>gcloud config get-value core/account&lt;span class="k">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="280" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_c1ddd68b32b467b2.webp 768w, https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_e44e1f902cf8419f.webp 480w, https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_797992c7ab4d6270.webp 1400w" src="https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_797992c7ab4d6270.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="install-the-operator-and-deploy-your-mongodb-cluster">Install the Operator and deploy your MongoDB cluster&lt;/h3>
&lt;ul>
&lt;li>Create a new namespace called &lt;strong>percona-demo-namespace&lt;/strong>&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create namespace percona-demo-namespace&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="926" height="242" sizes="(max-width: 926px) 100vw, 926px" srcset="https://percona.community/blog/2022/13/4-kubectl-create-namespace_hu_e1c24acb22448ebc.webp 768w, https://percona.community/blog/2022/13/4-kubectl-create-namespace_hu_50283e92e43981aa.webp 480w, https://percona.community/blog/2022/13/4-kubectl-create-namespace_hu_ef48c93e05bab85.webp 926w" src="https://percona.community/blog/2022/13/4-kubectl-create-namespace_hu_ef48c93e05bab85.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Set the context for the namespace&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl config set-context &lt;span class="k">$(&lt;/span>kubectl config current-context&lt;span class="k">)&lt;/span> --namespace&lt;span class="o">=&lt;/span>percona-demo-namespace&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1736" height="280" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_f08a6665413b1265.webp 768w, https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_80640e2865e49d45.webp 480w, https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_2f37a1482d863174.webp 1400w" src="https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_2f37a1482d863174.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Deploy the Operator&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.13.0/deploy/bundle.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="504" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_914c359efaed781a.webp 768w, https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_52772a64381b8970.webp 480w, https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_5a62d80c215ff177.webp 1400w" src="https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_5a62d80c215ff177.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>The operator has been started, and you can deploy your MongoDB cluster:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.13.0/deploy/cr.yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2038" height="280" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_4edb0c6b7a018bf7.webp 768w, https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_480cd027c2ae4d29.webp 480w, https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_70e48b1fc64f97ad.webp 1400w" src="https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_70e48b1fc64f97ad.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>When the process is over, your cluster will obtain the ready status. Check with:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl"> kubectl get psmdb.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1752" height="280" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_2dd2f3fd37e319b1.webp 768w, https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_7afc655f5a1e9fd5.webp 480w, https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_3addd18b81bcf548.webp 1400w" src="https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_3addd18b81bcf548.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> “psmdb” stands for &lt;a href="https://www.percona.com/software/mongodb/percona-server-for-mongodb" target="_blank" rel="noopener noreferrer">Percona Server for MongoDB&lt;/a>&lt;/p>
&lt;h3 id="verifying-the-cluster-operation">Verifying the cluster operation&lt;/h3>
&lt;ul>
&lt;li>You will need the login and password for the admin user to access the cluster. Use kubectl get secrets command to see the list of Secrets objects&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl get secret my-cluster-name-secrets -o yaml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1180" height="988" sizes="(max-width: 1180px) 100vw, 1180px" srcset="https://percona.community/blog/2022/13/9-kubectl-get-secret_hu_cb8e4de98a93ec0a.webp 768w, https://percona.community/blog/2022/13/9-kubectl-get-secret_hu_7dc4517df6d60bf6.webp 480w, https://percona.community/blog/2022/13/9-kubectl-get-secret_hu_8ef366c3d0f23784.webp 1180w" src="https://percona.community/blog/2022/13/9-kubectl-get-secret_hu_8ef366c3d0f23784.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Bring it back to a human-readable form to &lt;strong>MONGODB_DATABASE_ADMIN_PASSWORD&lt;/strong> and &lt;strong>MONGODB_DATABASE_ADMIN_USER&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="2022" height="354" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/10-decode_hu_b4009957949f568f.webp 768w, https://percona.community/blog/2022/13/10-decode_hu_6a3c439248069245.webp 480w, https://percona.community/blog/2022/13/10-decode_hu_5b549c944802a064.webp 1400w" src="https://percona.community/blog/2022/13/10-decode_hu_5b549c944802a064.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>We check the details of the Services, before testing the connection to the cluster&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1448" height="428" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/11-get-services_hu_8e8f1dea9ef8986f.webp 768w, https://percona.community/blog/2022/13/11-get-services_hu_c9e07ed90d63d53.webp 480w, https://percona.community/blog/2022/13/11-get-services_hu_f23eb09b653c0da8.webp 1400w" src="https://percona.community/blog/2022/13/11-get-services_hu_f23eb09b653c0da8.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Run a Docker container with a MongoDB client and connect its console output to your terminal. The following command will do this, naming the new Pod percona-client:&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl run -i --rm --tty percona-client --image&lt;span class="o">=&lt;/span>percona/percona-server-mongodb:4.4.16-16 --restart&lt;span class="o">=&lt;/span>Never -- bash -il&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="316" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/12-run-docker-container_hu_6735a9a545c1c774.webp 768w, https://percona.community/blog/2022/13/12-run-docker-container_hu_cddda282f2e1abfa.webp 480w, https://percona.community/blog/2022/13/12-run-docker-container_hu_1867bb18d5204c7a.webp 1400w" src="https://percona.community/blog/2022/13/12-run-docker-container_hu_1867bb18d5204c7a.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Now run mongo tool in the percona-client command shell using the login (which is normally clusterAdmin)&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mongo &lt;span class="s2">"mongodb://clusterAdmin:Dgqjc1HElUvvGnH9@my-cluster-name-mongos.percona-demo-namespace.svc.cluster.local/admin?ssl=false"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="950" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/13/13-mongo_hu_809002f0c9b9b67b.webp 768w, https://percona.community/blog/2022/13/13-mongo_hu_f4f05104b0841c2.webp 480w, https://percona.community/blog/2022/13/13-mongo_hu_136e2f807a2a0fcd.webp 1400w" src="https://percona.community/blog/2022/13/13-mongo_hu_136e2f807a2a0fcd.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Woolaaa!&lt;/strong> We have deployed MongoDB in Kubernetes using Operator, It works! &lt;strong>:)&lt;/strong>&lt;/p>
&lt;p>Now that you have the MongoDB cluster, you have full control to configure and manage MongoDB deployment from a single Kubernetes control plane, which means that you can manage MongoDB instances in the same way you manage default objects in Kubernetes like Deployments, Pods, or Services. For advanced configuration, topics see our guide &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/users.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a>.&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>Kubernetes Operators extend the Kubernetes API to automate processes that cannot be achieved natively with Kubernetes. This is the case for stateful applications like MongoDB.
Percona develops &lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB&lt;/a> that contains everything you need to quickly and consistently deploy and scale Percona Server for MongoDB instances into a Kubernetes cluster on-premises or in the cloud. You can try it on different cloud providers and &lt;a href="https://docs.percona.com/percona-operator-for-mongodb/#advanced-installation-guides" target="_blank" rel="noopener noreferrer">tutorials&lt;/a> for more advanced configurations.&lt;/p>
&lt;p>You can find &lt;strong>Percona Operator for MongoDB&lt;/strong> in Hacktoberfest! If you’re looking to improve your Kubernetes skills, this is a &lt;a href="https://www.percona.com/blog/contribute-to-open-source-with-percona-and-hacktoberfest/" target="_blank" rel="noopener noreferrer">great project to start contributing&lt;/a> to.&lt;/p>
&lt;p>We also have a &lt;strong>&lt;a href="https://github.com/percona/roadmap/projects/1" target="_blank" rel="noopener noreferrer">public roadmap&lt;/a>&lt;/strong> of Percona Kubernetes Operators. If you have any feedback or want to draw our attention to a particular feature, feel free to be part of it and vote for issues! :)&lt;/p>
&lt;p>&lt;strong>Resources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=HZ9yaS-ZS48&amp;t=2809s" target="_blank" rel="noopener noreferrer">Installation of MongoDB via Kubernetes Operator by Sergey Pronin - MongoDB Kubernetes operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.percona.com/percona-operator-for-mongodb/gke.html" target="_blank" rel="noopener noreferrer">Install Percona Server for MongoDB on Google Kubernetes Engine (GKE)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Server Mongodb Operator GitHub Repository&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cncf.io/blog/2022/06/15/kubernetes-operators-what-are-they-some-examples/#:~:text=K8s%20Operators%20are%20controllers%20for,Custom%20Resource%20Definitions%20%28CRD%29." target="_blank" rel="noopener noreferrer">Kubernetes Operators: what are they? Some examples CNCF.IO&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=i9V4oCa5f9I" target="_blank" rel="noopener noreferrer">Kubernetes Operators Explained by Sai Vennam&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://iximiuz.com/en/series/working-with-kubernetes-api/" target="_blank" rel="noopener noreferrer">Working with Kubernetes API Ivan Velichko&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Edith Puclla</author><category>kubernetes</category><category>operators</category><category>databases</category><category>mongodb</category><category>docker</category><media:thumbnail url="https://percona.community/blog/2022/13/with-operators_hu_bdd003d62e36fb66.jpg"/><media:content url="https://percona.community/blog/2022/13/with-operators_hu_59b4693fcad8842d.jpg" medium="image"/></item><item><title>Recap Monthly Percona Developer Meetup Hacktoberfest</title><link>https://percona.community/blog/2022/10/05/recap-monthly-percona-developer-meetup-hacktoberfest/</link><guid>https://percona.community/blog/2022/10/05/recap-monthly-percona-developer-meetup-hacktoberfest/</guid><pubDate>Wed, 05 Oct 2022 00:00:00 UTC</pubDate><description>The Monthly Percona Developer Meetup is an opportunity to get a behind-the-scenes view of different projects in Percona and directly interact with the experts to exchange ideas, ask questions, etc.</description><content:encoded>&lt;p>The &lt;a href="https://percona.community/blog/2022/09/26/monthly-percona-developer-meetup/" target="_blank" rel="noopener noreferrer">Monthly Percona Developer Meetup&lt;/a> is an opportunity to get a behind-the-scenes view of different projects in Percona and directly interact with the experts to exchange ideas, ask questions, etc.&lt;/p>
&lt;p>From now on, Monthly Percona Developer Meetups will take place monthly to have open discussions and in-person (online) communication. You can join our Developer Meetup via Restream, the &lt;a href="https://www.youtube.com/channel/UCLJ0Ok4HeUBrRYF4irturVA" target="_blank" rel="noopener noreferrer">Percona YouTube&lt;/a> channel, and &lt;a href="https://www.linkedin.com/company/percona/" target="_blank" rel="noopener noreferrer">LinkedIn&lt;/a> events.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="997" height="567" sizes="(max-width: 997px) 100vw, 997px" srcset="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_d637b924a68366e1.webp 768w, https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_3b72fc38214ebfe3.webp 480w, https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_9ffc0890100d15cc.webp 997w" src="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_9ffc0890100d15cc.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The topic of the first Monthly Percona Developer Meetup was &lt;a href="https://hacktoberfest.com/" target="_blank" rel="noopener noreferrer">Hacktoberfest&lt;/a>.&lt;/p>
&lt;p>&lt;strong>What is Hacktoberfest?&lt;/strong> Hacktoberfest is an annual event hosted by DigitalOcean that encourages people to contribute to Open Source throughout October. It is inclusive, everyone can join to work on Open Source projects, and you can participate by choosing your favorite project.&lt;/p>
&lt;p>You need two essential things to participate. First, register on the Hacktoberfest website anytime between September 26 and October 31, 2022. Second, look through the participating projects, choose your favorites, and consult the documentation guiding you to send your first contribution.&lt;/p>
&lt;p>Forty thousand participants who complete Hacktoberfest and have at least four pull/merge requests accepted between October 1 and October 31 can have a tree planted in their name or the Hacktoberfest T-shirt. In that case, you can pick a tree planted in your name or the Hacktoberfest 2022 T-shirt.&lt;/p>
&lt;p>Let’s ask the Percona experts!&lt;/p>
&lt;h2 id="which-percona-projects-joined-hacktoberfest">Which Percona projects joined Hacktoberfest?&lt;/h2>
&lt;p>All the &lt;a href="https://github.com/search?q=org%3Apercona+hacktoberfest" target="_blank" rel="noopener noreferrer">Percona GitHub Projects&lt;/a> with the label: “hacktoberfest” are ready to contribute.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="999" height="559" sizes="(max-width: 999px) 100vw, 999px" srcset="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-youtube_hu_84e844db61eefb15.webp 768w, https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-youtube_hu_9f0979bba3f9f7a.webp 480w, https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-youtube_hu_cb7c9ab350d590ad.webp 999w" src="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-youtube_hu_cb7c9ab350d590ad.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="how-do-i-find-issues">How do I find issues?&lt;/h2>
&lt;ul>
&lt;li>On &lt;strong>GitHub&lt;/strong>, the issues are tagged with the &lt;strong>good-first-issue&lt;/strong> tag.&lt;/li>
&lt;li>On Jira, tagged with &lt;strong>newbie&lt;/strong>, &lt;strong>hacktoberfest&lt;/strong> and &lt;strong>onboarding&lt;/strong>&lt;/li>
&lt;li>You can also pick any other issue you like or create new ones.&lt;/li>
&lt;/ul>
&lt;h2 id="what-types-of-contributions-count">What types of contributions count?&lt;/h2>
&lt;p>You can contribute in several ways: coding, documentation, testing, design, discussions, Content Creator (Blog posts, videos), etc.; they all count.&lt;/p>
&lt;h2 id="how-can-you-reach-the-percona-team">How can you reach the Percona team?&lt;/h2>
&lt;p>The best way to do it is directly in the Percona projects, or you can join our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Community Forum&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1665" height="860" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_e3f69cd386985d1.webp 768w, https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_ee26594e4aebda47.webp 480w, https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_e321e117a0605fb4.webp 1400w" src="https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_e321e117a0605fb4.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Look at some of the Percona projects added to Hacktoberfest this year.&lt;/p>
&lt;p>Starting for &lt;a href="https://github.com/percona/percona-docker" target="_blank" rel="noopener noreferrer">percona/percona-docker&lt;/a>. Supported by &lt;strong>Evgeniy Patlan&lt;/strong>, Manager, Build &amp; Release Engineering. There are images for basic scenarios. The idea is to create more images and improve the existing ones or extend them to Docker Compose. Also, contributions to improve the Docker setup are welcome. You can find issues in Jira or GitHub, where you can also participate in discussions about Percona Docker images.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="786" height="272" sizes="(max-width: 786px) 100vw, 786px" srcset="https://percona.community/blog/2022/10/recap-percona-docker_hu_d3b52e96444c84eb.webp 768w, https://percona.community/blog/2022/10/recap-percona-docker_hu_41cdbefcf6d5bb09.webp 480w, https://percona.community/blog/2022/10/recap-percona-docker_hu_d2ac2e18dc2ff75b.webp 786w" src="https://percona.community/blog/2022/10/recap-percona-docker_hu_d2ac2e18dc2ff75b.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our next project is &lt;a href="https://github.com/percona/pmm" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a>. This project is supported by &lt;strong>Artem Gavrilov&lt;/strong>, Backend Software Engineer, and &lt;strong>Nurlan Moldomurov&lt;/strong>, Full-Stack Engineer. PMM is a great project to contribute to during Hacktoberfest. There are minor and easy-to-do issues in &lt;a href="https://github.com/percona/pmm/issues" target="_blank" rel="noopener noreferrer">Github&lt;/a>; it is not necessary to register them in Jira. These contributions are welcome if you have any good ideas, want to improve something or simplify some process. Send your PR; the maintainers will review it as soon as possible. We also have advanced issues if you want to go for more advanced tasks.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1400" height="900" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/10/recap-pmm_hu_2b5bd73b9c5a3a9e.webp 768w, https://percona.community/blog/2022/10/recap-pmm_hu_2bf4e5f1c5cb85f6.webp 480w, https://percona.community/blog/2022/10/recap-pmm_hu_397f430bd610ed55.webp 1400w" src="https://percona.community/blog/2022/10/recap-pmm_hu_397f430bd610ed55.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For our third project, we have &lt;a href="https://github.com/percona/mongodb_exporter" target="_blank" rel="noopener noreferrer">percona/mongodb_exporter&lt;/a>. &lt;strong>Carlos Salguero&lt;/strong> is the maintainer for this project. He says it is a project easy to get started, and there is not much-complicated logic behind this.
It is about running some MongoDB internal commands to get statistics like diagnostic data or replica status, passing JSON to produce metrics from these commands. You use a complete Makefile to start sandbox instances to test almost everything; you don’t have a virtual machine or different MongoDB instances. The issues are in &lt;strong>GitHub&lt;/strong> and &lt;strong>Jira&lt;/strong>. The primary programming language is Go.&lt;/p>
&lt;p>It’s the turn of &lt;a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">percona/percona-server-mongodb-operator&lt;/a>; &lt;strong>Denys Kondratenko&lt;/strong> is the maintainer of this project. It is an excellent opportunity to learn about Kubernetes and how to extend it and maintain stateful databases inside Kubernetes. Most of the things are tracked in GitHub and Jira.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="900" height="424" sizes="(max-width: 900px) 100vw, 900px" srcset="https://percona.community/blog/2022/10/recap-operator_hu_7ca11da978e95c53.webp 768w, https://percona.community/blog/2022/10/recap-operator_hu_de6e9befa3352563.webp 480w, https://percona.community/blog/2022/10/recap-operator_hu_a76405246ed7ba34.webp 900w" src="https://percona.community/blog/2022/10/recap-operator_hu_a76405246ed7ba34.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our next project is &lt;a href="https://github.com/percona/pg_stat_monitor" target="_blank" rel="noopener noreferrer">percona/pg_stat_monitor&lt;/a>, a Query Performance Monitoring tool for PostgreSQL. The maintainer is &lt;strong>Ibrar Ahmed&lt;/strong>, Sr. Software Engineer (PostgreSQL). The primary area to contribute is the releases. If there are contributions with ideas on improving the information provided for &lt;strong>pg_stat_monitor&lt;/strong>, they are welcome. The issues are defined in Jira.&lt;/p>
&lt;p>&lt;strong>In summary&lt;/strong>, those are some of our projects that are in Hacktoberfest.
This month is Open Source month; it’s Hacktoberfest month party!&lt;/p>
&lt;p>Remember that you can interact with the maintainers of each project through &lt;a href="https://github.com/search?q=org%3Apercona+hacktoberfest" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>/&lt;a href="https://perconadev.atlassian.net/browse/DISTMYSQL-228?filter=-4" target="_blank" rel="noopener noreferrer">Jira&lt;/a> or our &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Community Forum&lt;/a>.&lt;/p>
&lt;p>If you haven’t sent a PR, this is the time to do it. Write us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> if you have any questions.&lt;/p>
&lt;p>Happy Hacktoberfest with Percona!&lt;/p></content:encoded><author>Edith Puclla</author><category>hacktoberfest</category><category>percona</category><category>databases</category><category>Meetup</category><media:thumbnail url="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_b03d7c312b7e80f8.jpg"/><media:content url="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro_hu_441ea955777e895b.jpg" medium="image"/></item><item><title>Open Source for Non-Techs - Find Your Way to Contribute!</title><link>https://percona.community/blog/2022/09/29/open-source-for-non-techs-find-your-way-to-contribute/</link><guid>https://percona.community/blog/2022/09/29/open-source-for-non-techs-find-your-way-to-contribute/</guid><pubDate>Thu, 29 Sep 2022 00:00:00 UTC</pubDate><description>When we talk about open source contributing, we often associate it with only and exclusively code contributions made by engineers. But the open source world is extensive and diverse, and everyone can find their place there. Even if you don’t feel confident with coding, you can have a lot of things to keep you busy! What can it give back to you? Lots of stuff: sharping your professional skills and obtaining diversified experience, enriching your portfolio, connecting with open source community and passionate people, and last but not the least - definitely lots of fun.</description><content:encoded>&lt;p>When we talk about open source contributing, we often associate it with only and exclusively code contributions made by engineers. But the open source world is extensive and diverse, and everyone can find their place there. Even if you don’t feel confident with coding, you can have a lot of things to keep you busy! What can it give back to you? Lots of stuff: sharping your professional skills and obtaining diversified experience, enriching your portfolio, connecting with open source community and passionate people, and last but not the least - definitely lots of fun.&lt;/p>
&lt;p>Many open source projects are maintained by enthusiasts, and they simply don’t have enough resources to keep an eye on everything. Here are some paths for you to consider, depending on your professional area and experience.&lt;/p>
&lt;h1 id="technical-writer--copy-editor--translator">Technical Writer / Copy Editor / Translator&lt;/h1>
&lt;p>As a professional who is good at working with text, you can have a lot of work to do. You can help with translation to different languages and localization to help users of your favorite open source project all over the world.&lt;/p>
&lt;p>Maintaining documentation is also a valuable skill, and it is s source of relentless worries for project maintainers. A lot of stuff might be needed - from simple how-tos for beginners to constant updates about new releases.&lt;/p>
&lt;p>You can also help with spreading the word about the project by posting technical content about it on different blogging platforms or on your personal blog. Themes could be:&lt;/p>
&lt;p>Your experience installing the software with different configurations.&lt;/p>
&lt;ul>
&lt;li>Basic configuration, best practices.&lt;/li>
&lt;li>Tuning and monitoring of the tool.&lt;/li>
&lt;li>Lifehacks, or advanced options for experienced users.&lt;/li>
&lt;li>How this tool helps you in your business processes and goals.&lt;/li>
&lt;li>Possible alternatives or digests of the best-in-class tools.&lt;/li>
&lt;/ul>
&lt;h1 id="tester--end-user">Tester / End User&lt;/h1>
&lt;p>You can do what no one else can - provide valuable feedback looking from the position of the software user. Open GitHub/Jira issues and describe unexpected/strange behavior to report bugs providing as much information about your configuration and user scenario as you can. Also, you can create feature requests with ideas about future project development and why you think that may be important not just for your use case, but for many.&lt;/p>
&lt;h1 id="designer">Designer&lt;/h1>
&lt;p>Depending on your professional focus, there is a wide range of involvement for you - from UX/UI improvements to banner/promo graphics. Not all open source projects have an opportunity to involve a designer in their development. Give them a helping hand and improve any aspects of your favorite projects. That is also a good way to add interesting projects to your portfolio.&lt;/p>
&lt;h1 id="influencer--advocate">Influencer / Advocate&lt;/h1>
&lt;p>There are lots of things that you can do! Share posts on social media (Facebook, Twitter, LinkedIn, etc.), record YouTube shorts or even do live streams about the software you like. If you have a podcast, you can invite maintainers to discuss different aspects of their project, its value and future perspectives. Active speakers can even do presentations and talks about open source tools! Such marketing and community support is extremely valuable. It can be a big resource for the project’s growth and promotion, attract new users or help to onboard and keep them.&lt;/p>
&lt;h1 id="lets-start">Let’s Start!&lt;/h1>
&lt;p>So, let’s roll up our sleeves, we have a lot of work to do! And if you doubt where to start, look at the opportunities provided by &lt;a href="https://hacktoberfest.com/about/#low-or-non-code" target="_blank" rel="noopener noreferrer">Hacktoberfest&lt;/a>. Start your open source journey with &lt;a href="https://www.percona.com/blog/contribute-to-open-source-with-percona-and-hacktoberfest/" target="_blank" rel="noopener noreferrer">Percona x Hacktoberfest&lt;/a> and receive some nice prizes!&lt;/p>
&lt;p>If you made a non-code contribution during Hacktoberfest, just create a Pull Request to our &lt;a href="https://github.com/percona/community/" target="_blank" rel="noopener noreferrer">Percona Community repository&lt;/a> with links to your work. Add your contribution to our list of &lt;a href="https://percona.community/contribute/articles/" target="_blank" rel="noopener noreferrer">blog posts&lt;/a> and &lt;a href="https://percona.community/contribute/videos/" target="_blank" rel="noopener noreferrer">videos&lt;/a>, and follow the process of &lt;a href="https://percona.community/contribute/documentation/" target="_blank" rel="noopener noreferrer">submitting changes to documentation&lt;/a>. Other types of contributions (custom dashboards, configuration files, etc.) can be added to our &lt;a href="https://percona.community/contribute/dev/" target="_blank" rel="noopener noreferrer">developer artifacts collection&lt;/a>.&lt;/p>
&lt;p>Alternatively, you can contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a>, and we will guide you through the process.&lt;/p></content:encoded><author>Aleksandra Abramova</author><category>Community</category><category>Opensource</category><media:thumbnail url="https://percona.community/blog/2022/9/Non-Tech_hu_18537297d863ce15.jpg"/><media:content url="https://percona.community/blog/2022/9/Non-Tech_hu_83d291d8c8010c9.jpg" medium="image"/></item><item><title>Monthly Percona Developer Meetup</title><link>https://percona.community/blog/2022/09/26/monthly-percona-developer-meetup/</link><guid>https://percona.community/blog/2022/09/26/monthly-percona-developer-meetup/</guid><pubDate>Mon, 26 Sep 2022 00:00:00 UTC</pubDate><description>What is this? This new meetup is your chance to get a behind-the-scenes view and to interact directly in person with our engineers. You can jump into the session and discuss with our engineers without any barriers in between. If you do have questions, new ideas, enhancements, or are just curious to learn more, you’re welcome to join our call.</description><content:encoded>&lt;h2 id="what-is-this">What is this?&lt;/h2>
&lt;p>This new meetup is your chance to get a behind-the-scenes view and to interact directly in person with our engineers. You can jump into the session and discuss with our engineers without any barriers in between. If you do have questions, new ideas, enhancements, or are just curious to learn more, you’re welcome to join our call.&lt;/p>
&lt;p>We’ll discuss development practices, tools, projects, frameworks, and many more engineering-focused topics we are working on at Percona.&lt;/p>
&lt;h2 id="why">Why?&lt;/h2>
&lt;p>We want to be as open and transparent as we can with our community and users. Transparency and openness is the key element to building and fostering a healthy community. Direct in-person interaction is invaluable and a great channel for sharing ideas and receiving feedback.&lt;/p>
&lt;h2 id="when">When?&lt;/h2>
&lt;p>The meetup will be scheduled once a month, and we’ll announce it in advance so that you can plan your schedule accordingly.&lt;/p>
&lt;h2 id="where">Where?&lt;/h2>
&lt;p>This event happens virtually via Restream and everyone can join using the published links through our channels.&lt;/p>
&lt;h2 id="how-do-we-start">How do we start?&lt;/h2>
&lt;p>The kick-off session is going to be this Wednesday, 28th of September 14:30 UTC. We are going to discuss Hacktoberfest in detail, what this is about, and how you can contribute to it. Representatives from various Percona projects will be there to give you some insights into their projects and how you could help make them even better. For more information and a detailed agenda, please look at our &lt;a href="https://percona.community/events/streams-monthly-dev/2022-09-26-hacktoberfest/" target="_blank" rel="noopener noreferrer">announcement&lt;/a>.&lt;/p></content:encoded><author>Kai Wagner</author><category>Community</category><category>Meetup</category><category>Engineering</category><category>Developer</category><media:thumbnail url="https://percona.community/blog/2022/9/Blog-Meetup-Cover_hu_7bfa43b47c9ed572.jpg"/><media:content url="https://percona.community/blog/2022/9/Blog-Meetup-Cover_hu_5d7f34e9b6b7e497.jpg" medium="image"/></item><item><title>Announcing the release of pg_stat_monitor 1.1.0</title><link>https://percona.community/blog/2022/09/22/announcing-the-release-of-pg_stat_monitor-1.1.0/</link><guid>https://percona.community/blog/2022/09/22/announcing-the-release-of-pg_stat_monitor-1.1.0/</guid><pubDate>Thu, 22 Sep 2022 00:00:00 UTC</pubDate><description>Percona is happy to announce the 1.1.0 release of pg_stat_monitor. You can install it from the Percona repositories following the installation instructions.</description><content:encoded>&lt;p>Percona is happy to announce the 1.1.0 release of &lt;a href="https://github.com/percona/pg_stat_monitor" target="_blank" rel="noopener noreferrer">pg_stat_monitor&lt;/a>. You can install it from the Percona repositories following the &lt;a href="https://docs.percona.com/postgresql/14/pg-stat-monitor.html#installation" target="_blank" rel="noopener noreferrer">installation instructions&lt;/a>.&lt;/p>
&lt;p>pg_stat_monitor is a Query Performance Monitoring tool for PostgreSQL. It attempts to provide a more holistic picture by providing much-needed query performance insights in a single view.&lt;/p>
&lt;p>pg_stat_monitor provides improved insights that allow database users to understand query origins, execution, planning statistics and details, query information, and metadata. This significantly improves observability, enabling users to debug and tune query performance. pg_stat_monitor is developed on the basis of pg_stat_statements as its more advanced replacement.&lt;/p>
&lt;p>&lt;strong>Key enhancements in this release:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>The bucket start times are now aligned according to bucket time size units.&lt;/li>
&lt;li>pgsm_normalized_query GUC has been enabled by default now, allowing query parameter values to be available for improved observability. (Note: This option can be disabled at the server start if this behavior is not required, as it does incur a small performance penalty)&lt;/li>
&lt;li>Histogram of query execution time: Track and visualize query execution variability to gain better insight.
For a complete list of changes, please refer to the &lt;a href="https://github.com/percona/pg_stat_monitor/releases/tag/1.1.0" target="_blank" rel="noopener noreferrer">release notes&lt;/a>.&lt;/li>
&lt;/ul></content:encoded><category>Postgres</category><category>PostgreSQL</category><category>Developer</category><media:thumbnail url="https://percona.community/blog/2022/9/pg_stat_monitor_hu_6a67a4c8d8b2f1b9.jpeg"/><media:content url="https://percona.community/blog/2022/9/pg_stat_monitor_hu_17788ec05bea5e53.jpeg" medium="image"/></item><item><title>Percona Monitoring and Management 2.31 preview release</title><link>https://percona.community/blog/2022/09/19/preview-release/</link><guid>https://percona.community/blog/2022/09/19/preview-release/</guid><pubDate>Mon, 19 Sep 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.31 preview release Hello folks! Percona Monitoring and Management (PMM) 2.31 is now available as a preview release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-231-preview-release">Percona Monitoring and Management 2.31 preview release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.31 is now available as a preview release.&lt;/p>
&lt;p>We encourage you to try this PMM preview release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release notes can be found in &lt;a href="https://pmm-v2-31-0-pr-868.onrender.com/release-notes/2.31.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="known-issue">Known issue&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-10735" target="_blank" rel="noopener noreferrer">PMM-10735&lt;/a>: OVF stopped working in a few minutes.&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker">Percona Monitoring and Management server docker&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.31.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In order to use the DBaaS functionality during the Percona Monitoring and Management preview release, you should add the following environment variablewhen starting PMM server:&lt;/p>
&lt;p>&lt;code>PERCONA_TEST_DBAAS_PMM_CLIENT=perconalab/pmm-client:2.31.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client release candidate tarball for 2.31 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4348.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable percona testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;hr></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Creating a Kubernetes cluster on Amazon EKS with eksctl</title><link>https://percona.community/blog/2022/09/13/creating-a-kubernetes-cluster-on-amazon-eks-with-eksctl/</link><guid>https://percona.community/blog/2022/09/13/creating-a-kubernetes-cluster-on-amazon-eks-with-eksctl/</guid><pubDate>Tue, 13 Sep 2022 00:00:00 UTC</pubDate><description>Amazon Elastic Kubernetes Service (Amazon EKS) is a managed Kubernetes service that makes it easy for you to run Kubernetes on AWS and on-premises. Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. Amazon EKS is certified Kubernetes-conformant, so existing applications that run on upstream Kubernetes are compatible with Amazon EKS.</description><content:encoded>&lt;p>&lt;a href="https://aws.amazon.com/eks/" target="_blank" rel="noopener noreferrer">Amazon Elastic Kubernetes Service&lt;/a> (Amazon EKS) is a managed Kubernetes service that makes it easy for you to run Kubernetes on AWS and on-premises. &lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a> is an open-source system for automating deployment, scaling, and management of containerized applications. Amazon EKS is certified Kubernetes-conformant, so existing applications that run on upstream Kubernetes are compatible with Amazon EKS.&lt;/p>
&lt;p>Getting started guides available in the AWS documentation explain two different procedures for creating an EKS cluster. One using eksctl, a simple command line utility for creating and managing Kubernetes clusters on Amazon EKS, and the other one using the AWS Management Console and AWS CLI.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html" target="_blank" rel="noopener noreferrer">Getting started with Amazon EKS - eksctl&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html" target="_blank" rel="noopener noreferrer">Getting started with Amazon EKS – AWS Management Console and AWS CLI&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Through this article, you will learn how to use eksctl for creating a Kubernetes cluster on Amazon EKS.&lt;/p>
&lt;p>&lt;a href="https://eksctl.io" target="_blank" rel="noopener noreferrer">eksctl&lt;/a> is a simple CLI tool for creating and managing clusters on EKS - Amazon’s managed Kubernetes service for EC2. It is written in Go, uses CloudFormation, and was created by &lt;a href="https://www.weave.works/" target="_blank" rel="noopener noreferrer">Weaveworks&lt;/a>.&lt;/p>
&lt;p>For using eksctl, you must:.&lt;/p>
&lt;ul>
&lt;li>Install &lt;a href="https://kubernetes.io/docs/reference/kubectl/" target="_blank" rel="noopener noreferrer">kubectl&lt;/a>.&lt;/li>
&lt;li>Install &lt;a href="https://github.com/kubernetes-sigs/aws-iam-authenticator" target="_blank" rel="noopener noreferrer">AWS IAM Authenticator for Kubernetes&lt;/a>.&lt;/li>
&lt;li>Install &lt;a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener noreferrer">AWS CLI&lt;/a>.&lt;/li>
&lt;li>Create a user with &lt;a href="https://eksctl.io/usage/minimum-iam-policies/" target="_blank" rel="noopener noreferrer">minimal IAM policies&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>After running eksctl, you will get a cluster with default configuration:&lt;/p>
&lt;ul>
&lt;li>Exciting auto-generated name&lt;/li>
&lt;li>Two m5.large worker nodes&lt;/li>
&lt;li>Use the official AWS EKS AMI&lt;/li>
&lt;li>Default us-west-2 region&lt;/li>
&lt;li>A dedicated VPC&lt;/li>
&lt;/ul>
&lt;h2 id="creating-iam-user">Creating IAM user&lt;/h2>
&lt;p>Go to &lt;a href="https://console.aws.amazon.com/iamv2" target="_blank" rel="noopener noreferrer">console.aws.amazon.com/iamv2&lt;/a>, create a user group, named EKS, and attach the policies described in the &lt;a href="https://eksctl.io/usage/minimum-iam-policies/" target="_blank" rel="noopener noreferrer">minimal IAM policies&lt;/a> section from the eksctl documentation.&lt;/p>
&lt;p>These policies already exist, and you must attach them as they are.&lt;/p>
&lt;ul>
&lt;li>AmazonEC2FullAccess (AWS managed)&lt;/li>
&lt;li>AWSCloudFormationFullAccess (AWS managed)&lt;/li>
&lt;/ul>
&lt;p>In addition to previous policies, you must create:&lt;/p>
&lt;details>
&lt;summary>&lt;b>EksAllAccess&lt;/b> (click to expand)&lt;/summary>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Version": "2012-10-17",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Statement": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": "eks:*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": "*"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "ssm:GetParameter",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "ssm:GetParameters"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:ssm:*:&lt;account_id>:parameter/aws/*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:ssm:*::parameter/aws/*"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "kms:CreateGrant",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "kms:DescribeKey"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": "*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "logs:PutRetentionPolicy"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": "*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/details>
&lt;details>
&lt;summary>&lt;b>IAMLimitedAccess&lt;/b> (click to expand)&lt;/summary>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Version": "2012-10-17",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Statement": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:CreateInstanceProfile",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DeleteInstanceProfile",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetInstanceProfile",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:RemoveRoleFromInstanceProfile",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:CreateRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DeleteRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:AttachRolePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:PutRolePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:ListInstanceProfiles",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:AddRoleToInstanceProfile",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:ListInstanceProfilesForRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:PassRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DetachRolePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DeleteRolePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetRolePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetOpenIDConnectProvider",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:CreateOpenIDConnectProvider",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DeleteOpenIDConnectProvider",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:TagOpenIDConnectProvider",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:ListAttachedRolePolicies",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:TagRole",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetPolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:CreatePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:DeletePolicy",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:ListPolicyVersions"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:instance-profile/eksctl-*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:role/eksctl-*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:policy/eksctl-*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:oidc-provider/*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:role/eksctl-managed-*"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:GetRole"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "arn:aws:iam::&lt;account_id>:role/*"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Effect": "Allow",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Action": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:CreateServiceLinkedRole"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Resource": "*",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "Condition": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "StringEquals": {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "iam:AWSServiceName": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "eks.amazonaws.com",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "eks-nodegroup.amazonaws.com",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "eks-fargate.amazonaws.com"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/details>
&lt;p>Replace &lt;code>&lt;account_id>&lt;/code>, in both policies, with your AWS account ID, you can find it in the upper right corner, in the navigation bar. For other ways of getting your account ID, go to &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html" target="_blank" rel="noopener noreferrer">Your AWS account ID and its alias&lt;/a> in the docs.&lt;/p>
&lt;p>Add a new user, named eksctl, to the group previously created.&lt;/p>
&lt;p>Don’t forget to download or copy your credentials, Access Key ID and Secret Access Key, as you will need them for setting up authentication.&lt;/p>
&lt;h2 id="installing-aws-cli">Installing AWS CLI&lt;/h2>
&lt;p>On Linux, download the installer:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Unzip the installer:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ unzip awscliv2.zip&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And run the installer:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo ./aws/install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For instructions on how to install AWS CLI on other operating systems, go to &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" target="_blank" rel="noopener noreferrer">Installing or updating the latest version of the AWS CLI&lt;/a> in the documentation.&lt;/p>
&lt;p>After installing AWS CLI, run the following command for setting up authentication locally:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ aws configure --profile eksctl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It will ask you for your AWS credentials and default region.&lt;/p>
&lt;h2 id="installing-aws-iam-authenticator">Installing AWS IAM Authenticator&lt;/h2>
&lt;p>On Linux, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -o aws-iam-authenticator https://s3.us-west-2.amazonaws.com/amazon-eks/1.21.2/2021-07-05/bin/linux/amd64/aws-iam-authenticator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply execute permissions to the binary:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ chmod +x ./aws-iam-authenticator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a folder in your &lt;code>$HOME&lt;/code> directory and add it to the &lt;code>$PATH&lt;/code> variable:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ mkdir -p $HOME/bin &amp;&amp; cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator &amp;&amp; export PATH=$PATH:$HOME/bin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add &lt;code>$HOME/bin&lt;/code> to your &lt;code>.bashrc&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For Mac and Windows, check &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html" target="_blank" rel="noopener noreferrer">Installing aws-iam-authenticator&lt;/a> in the documentation.&lt;/p>
&lt;h2 id="installing-kubectl">Installing kubectl&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>&lt;em>NOTE:&lt;/em>&lt;/strong> From the documentation - You must use a &lt;code>kubectl&lt;/code> version that is within one minor version difference of your Amazon EKS cluster control plane. For example, a &lt;code>1.22&lt;/code> &lt;code>kubectl&lt;/code> client works with Kubernetes &lt;code>1.21&lt;/code>, &lt;code>1.22&lt;/code>, and &lt;code>1.23&lt;/code> clusters.&lt;/p>&lt;/blockquote>
&lt;p>As of today, the latest version of Kubernetes used by eksctl is 1.21. Run the following command for installing the corresponding version of kubectl:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.21.2/2021-07-05/bin/linux/amd64/kubectl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Apply execute permissions to the binary:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ chmod +x ./kubectl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Copy the binary to &lt;code>$HOME/bin&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cp ./kubectl $HOME/bin/kubectl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you’re using another version of Kubernetes, check &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html" target="_blank" rel="noopener noreferrer">Installing or updating kubectl&lt;/a> in the documentation, where you can also find instructions for other operating systems.&lt;/p>
&lt;h2 id="installing-eksctl-and-creating-a-kubernetes-cluster">Installing eksctl and creating a Kubernetes cluster&lt;/h2>
&lt;p>Download the binary and copy it to &lt;code>/usr/local/bin&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo mv /tmp/eksctl /usr/local/bin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>On Mac and Windows, you can install eksctl following the instructions in the GitHub &lt;a href="https://github.com/weaveworks/eksctl" target="_blank" rel="noopener noreferrer">repository&lt;/a>.&lt;/p>
&lt;p>Once installed, create a cluster with default configuration, and authenticate to AWS using IAM user created previously.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ eksctl create cluster --profile eksctl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>&lt;em>NOTE:&lt;/em>&lt;/strong> From the documentation - That command will create an EKS cluster in your default region (as specified by your AWS CLI configuration) with one managed nodegroup containing two m5.large nodes.&lt;/p>&lt;/blockquote>
&lt;p>For a cluster with custom configuration, create a config file, named &lt;code>cluster.yaml&lt;/code>, with the following content:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">eksctl.io/v1alpha5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterConfig&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">basic-cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">region&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">eu-north-1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">nodeGroups&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ng-1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">instanceType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">m5.large&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">desiredCapacity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeSize&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ssh&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allow&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># will use ~/.ssh/id_rsa.pub as the default ssh key&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ng-2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">instanceType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">m5.xlarge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">desiredCapacity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeSize&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ssh&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">publicKeyPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">~/.ssh/ec2_id_rsa.pub&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run eksctl to create the cluster as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ eksctl create cluster -f cluster.yaml --profile eksctl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>While running, eksctl will create the cluster and all the necessary resources.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1920" height="1096" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/9/eksctl_running_hu_c125d980722a457a.webp 768w, https://percona.community/blog/2022/9/eksctl_running_hu_ea4267dd2383e28f.webp 480w, https://percona.community/blog/2022/9/eksctl_running_hu_eb068c90d2089179.webp 1400w" src="https://percona.community/blog/2022/9/eksctl_running_hu_eb068c90d2089179.webp" alt="eksctl running" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>It will take a few minutes to complete. After the command is executed, you can go to &lt;a href="https://us-east-1.console.aws.amazon.com/eks/home?region=us-east-1#/clusters" target="_blank" rel="noopener noreferrer">us-east-1.console.aws.amazon.com/eks/home?region=us-east-1#/clusters&lt;/a> to see the cluster.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1920" height="911" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/9/eks_cluster_hu_d07b3d9407d19189.webp 768w, https://percona.community/blog/2022/9/eks_cluster_hu_22aebd3a179e21bc.webp 480w, https://percona.community/blog/2022/9/eks_cluster_hu_7332c439be1680ec.webp 1400w" src="https://percona.community/blog/2022/9/eks_cluster_hu_7332c439be1680ec.webp" alt="EKS Cluster" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Don’t forget to replace &lt;code>us-east-1&lt;/code> in the URL, if your default region is different.&lt;/p>
&lt;p>Cluster credentials can be found in &lt;code>~/.kube/config&lt;/code>. Try &lt;code>kubectl get nodes&lt;/code> to verify that this file is valid, as suggested by eksctl.&lt;/p>
&lt;p>If, for any reason, you need to delete your cluster, just run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ eksctl delete cluster --name=ferocious-painting-1660755039 --profile eksctl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>name&lt;/code> with corresponding value.&lt;/p>
&lt;p>You’ve created your first Kubernetes cluster using eksctl. Check the documentation for more information on how to &lt;a href="https://eksctl.io/usage/creating-and-managing-clusters/" target="_blank" rel="noopener noreferrer">create and manage clusters&lt;/a>.&lt;/p></content:encoded><author>Mario García</author><category>Linux</category><category>Kubernetes</category><category>AWS</category><category>Amazon EKS</category><media:thumbnail url="https://percona.community/blog/2022/9/eksctl_running_hu_b6470d877e89d74e.jpg"/><media:content url="https://percona.community/blog/2022/9/eksctl_running_hu_44229ef62aeab1fe.jpg" medium="image"/></item><item><title>Running PMM with Docker on Ubuntu 20.04</title><link>https://percona.community/blog/2022/08/05/installing-pmm-with-docker-on-ubuntu-20/</link><guid>https://percona.community/blog/2022/08/05/installing-pmm-with-docker-on-ubuntu-20/</guid><pubDate>Fri, 05 Aug 2022 00:00:00 UTC</pubDate><description>I started at Percona a few weeks ago and was looking for a quick way to learn about PMM (Percona Monitoring and Management), which is one of my favorite technologies within Percona to monitor the health of our database infrastructure, explore new patterns in the database behavior, manage and improve the performance of our databases, all with customizable dashboards and real-time alerts using Grafana and VictoriaMetrics.</description><content:encoded>&lt;p>I started at Percona a few weeks ago and was looking for a quick way to learn about PMM (Percona Monitoring and Management), which is one of my favorite technologies within Percona to monitor the health of our database infrastructure, explore new patterns in the database behavior, manage and improve the performance of our databases, all with customizable dashboards and real-time alerts using &lt;a href="https://grafana.com/" target="_blank" rel="noopener noreferrer">Grafana&lt;/a> and &lt;a href="https://victoriametrics.com/" target="_blank" rel="noopener noreferrer">VictoriaMetrics&lt;/a>.&lt;/p>
&lt;p>The best of all is that PMM is Open Source, you can check the &lt;a href="https://github.com/percona/pmm" target="_blank" rel="noopener noreferrer">PMM repository&lt;/a> in case you want to contribute.&lt;/p>
&lt;p>There are many flavors for PMM installation, here I will describe the steps to install PMM on Ubuntu 20.04, using Docker for PMM Server on an Amazon EC2 instance.&lt;/p>
&lt;p>This image summarizes our goal.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1661" height="1416" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_7a8fd5451b219b00.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_dc30e35d3f35bb22.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_c792b40c2c6f3a91.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_c792b40c2c6f3a91.webp" alt="Overview" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="requirements">Requirements&lt;/h2>
&lt;ul>
&lt;li>An Amazon EC2 instance with Ubuntu 20.04
&lt;ul>
&lt;li>This instance is configured with a Security Group with TCP port 443 open.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Docker
&lt;ul>
&lt;li>You can install Docker by following this &lt;a href="https://docs.docker.com/engine/install/ubuntu/" target="_blank" rel="noopener noreferrer">guide&lt;/a>.&lt;/li>
&lt;li>Manage Docker as a non-root user: &lt;strong>&lt;em>sudo usermod -aG docker $USER&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>MySQL
&lt;ul>
&lt;li>I am using Percona Server for MySQL from &lt;a href="https://docs.percona.com/percona-server/8.0/installation/apt_repo.html" target="_blank" rel="noopener noreferrer">Percona apt repository&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="installing-pmm-server-with-docker">Installing PMM Server with Docker&lt;/h2>
&lt;ol>
&lt;li>Download PMM server Docker image&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker pull percona/pmm-server:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Create the data volume container&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker create --volume /srv --name pmm-data percona/pmm-server:2 /bin/true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Run PMM server container&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run --detach --restart always --publish 443:443 --volumes-from pmm-data --name pmm-server percona/pmm-server:2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Verify the creation of the container.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker ps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1940" height="454" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_889529d1f806a9f2.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_63cb7778aba3a50c.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_a9b3df6ab4cf8e4d.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_a9b3df6ab4cf8e4d.webp" alt="docker ps" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Start a web browser and in the address bar enter the IP address of the &lt;strong>PMM server&lt;/strong> host: https://&lt;PUBLIC_IP>:443/. For example, https://172.31.53.46. If you are running on your local machine use https://localhost:443/.
Woohoo! We have a PMM Server running and we can see our dashboard!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1427" height="874" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_d9539958d8592488.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_a2c54345622038a7.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_6f67a74220bce9d9.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_6f67a74220bce9d9.webp" alt="pmm-ubuntu-pmm-dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> Some browsers may not trust the self-signed SSL certificate when you first open the URL. If this is the case, Chrome users may want to type &lt;strong>thisisunsafe&lt;/strong> to bypass the warning.&lt;/p>
&lt;p>The user and password are &lt;strong>“admin”&lt;/strong> and &lt;strong>“admin”&lt;/strong>, It will ask you to change the password after login in for the first time, for this demo I will use &lt;strong>admin2020&lt;/strong> as a password. We will use these credentials to register the node in PMM Server later.&lt;/p>
&lt;p>Until now we have only PMM Server. To monitor a database, we need a PMM client.&lt;/p>
&lt;h2 id="installing-pmm-client">Installing PMM client&lt;/h2>
&lt;p>&lt;strong>PMM Client&lt;/strong> is a collection of agents and exporters that run on the host being monitored. Let´s install it using the repository package.&lt;/p>
&lt;ol>
&lt;li>Download Percona Repo Package&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wget https://repo.percona.com/apt/percona-release_latest.&lt;span class="k">$(&lt;/span>lsb_release -sc&lt;span class="k">)&lt;/span>_all.deb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="2">
&lt;li>Install Percona Repo Package&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt install ./percona-release_latest.&lt;span class="k">$(&lt;/span>lsb_release -sc&lt;span class="k">)&lt;/span>_all.deb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Update apt cache&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt update&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="4">
&lt;li>Install Percona Monitoring and Management Client&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt install pmm2-client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="5">
&lt;li>Checking the installation. We will use pmm-admin in the next steps.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pmm-admin -v&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1146" height="566" sizes="(max-width: 1146px) 100vw, 1146px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-v_hu_38958f3d13923f50.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-v_hu_4960e237bbe1d664.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-v_hu_9ea241879b47ef08.webp 1146w" src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-v_hu_9ea241879b47ef08.webp" alt="pmm-ubuntu-pmm-dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="creating-a-user-for-monitoring">Creating a user for monitoring&lt;/h2>
&lt;p>Let’s create a user in MySQL.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Login in MySQL for use the command-line: &lt;strong>&lt;em>mysql -uroot -p&lt;/em>&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a “pmm” user with “welcOme1!” As a password&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">CREATE USER &lt;span class="s1">'pmm'&lt;/span>@&lt;span class="s1">'localhost'&lt;/span> IDENTIFIED BY &lt;span class="s1">'welcOme1!'&lt;/span> WITH MAX_USER_CONNECTIONS 10&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>Give “pmm” user with specific permission to monitor the database&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">GRANT SELECT, PROCESS, REPLICATION CLIENT, RELOAD, BACKUP_ADMIN ON *.* TO &lt;span class="s1">'pmm'&lt;/span>@&lt;span class="s1">'localhost'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Checking if the user was created correctly with the respective permissions, use&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl"> show grants &lt;span class="k">for&lt;/span> &lt;span class="s1">'pmm'&lt;/span>@&lt;span class="s1">'localhost'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="1636" height="678" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_dafaab468c9283ad.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_efb8a1eeb8d85e3d.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_171f1acd8d46240c.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_171f1acd8d46240c.webp" alt="pmm-ubuntu-show-grants" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="connect-client-to-server">Connect Client to Server&lt;/h2>
&lt;ol>
&lt;li>Register Percona Monitoring and Management client with server, use the default admin/admin username and password.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pmm-admin config --server-insecure-tls --server-url&lt;span class="o">=&lt;/span>https://admin:admin2020@172.17.0.1:443&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Note:&lt;/strong> I am using &lt;strong>172.17.0.1&lt;/strong> because this is the private IP where the PMM Server is running. You can get this IP by entering the docker container and typing &lt;strong>“hostname -I”&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="924" height="446" sizes="(max-width: 924px) 100vw, 924px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-hostname-i_hu_1c801af2775533be.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-hostname-i_hu_a1f5a52cffa099bd.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-hostname-i_hu_f135eb9badf37cca.webp 924w" src="https://percona.community/blog/2022/8/pmm-ubuntu-hostname-i_hu_f135eb9badf37cca.webp" alt="pmm-ubuntu-hostname-i" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After registering your client with the server you will see this information:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2048" height="782" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_3a74f2abff23aed9.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_6b87174be68d870f.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_a07790e1dc1238cb.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_a07790e1dc1238cb.webp" alt="pmm-ubuntu-pmm-admin-config" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>Check if the node was registered&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pmm-admin inventory list nodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A new node should appear in the list which is &lt;strong>pmm-server&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1952" height="596" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_16a111c620c6f347.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_880e35842fd419cc.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_4b91f4295c7fc3d.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_4b91f4295c7fc3d.webp" alt="pmm-ubuntu-hostname-i" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="adding-a-mysql-database-to-monitoring">Adding a MySQL Database to monitoring&lt;/h2>
&lt;ol>
&lt;li>Use pmm-admin to register the database with the user we created in MySQL&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pmm-admin add mysql --username&lt;span class="o">=&lt;/span>pmm --password&lt;span class="o">=&lt;/span>welcOme1! --query-source&lt;span class="o">=&lt;/span>perfschema&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="2048" height="632" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_8a78c64d9822e570.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_dcc9b4aa4c087529.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_b06abad758d07276.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_b06abad758d07276.webp" alt="pmm-ubuntu-pmm-admin-add-sql" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>In the dashboard, we will see that our node and database are registered and ready to be monitored by PMM.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2888" height="1724" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_b891748a5ba13f63.webp 768w, https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_97f461905e8de136.webp 480w, https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_f63d5563c2d08d3c.webp 1400w" src="https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_f63d5563c2d08d3c.webp" alt="pmm-ubuntu-last-dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>That’s it! :) We learned how to monitor our databases for free with Percona Monitoring Database (PMM). Additionally, you can go to the next level by registering a PMM instance with &lt;a href="https://docs.percona.com/percona-platform/" target="_blank" rel="noopener noreferrer">Percona Platform&lt;/a> and receive more information.&lt;/p>
&lt;p>I hope you’ve enjoyed this tutorial, and if you need help following it, feel free to contact the &lt;a href="https://percona.community/blog/2022/02/10/how-to-publish-blog-post/#assistance-and-support" target="_blank" rel="noopener noreferrer">Percona team support&lt;/a>. We will be happy to help.&lt;/p></content:encoded><author>Edith Puclla</author><category>PMM</category><category>DevOps</category><category>MySQL</category><category>Docker</category><media:thumbnail url="https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_5ad125c14b3da007.jpg"/><media:content url="https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_6bd40fe72fd83535.jpg" medium="image"/></item><item><title>Setting up PMM for monitoring MySQL on a local environment</title><link>https://percona.community/blog/2022/08/05/setting-up-pmm-for-monitoring-mysql-on-a-local-environment/</link><guid>https://percona.community/blog/2022/08/05/setting-up-pmm-for-monitoring-mysql-on-a-local-environment/</guid><pubDate>Fri, 05 Aug 2022 00:00:00 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="1920" height="1072" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/8/pmm-dashboard_hu_539b12ed1ab512e1.webp 768w, https://percona.community/blog/2022/8/pmm-dashboard_hu_8aaf0a1a57ce2f5e.webp 480w, https://percona.community/blog/2022/8/pmm-dashboard_hu_4356dabbab5902b4.webp 1400w" src="https://percona.community/blog/2022/8/pmm-dashboard_hu_4356dabbab5902b4.webp" alt="PMM Dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Percona Monitoring and Management (&lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">PMM&lt;/a>) is an open source database monitoring, observability, and management tool that can be used for monitoring the health of your database infrastructure, exploring new patterns in database behavior, and managing and improving the performance of your databases no matter where they are located or deployed.&lt;/p>
&lt;p>PMM is designed to work with MySQL (including Percona Server for MySQL, Percona XtraDB Cluster, Oracle MySQL Community Edition, Oracle MySQL Enterprise Edition, and MariaDB), PostgreSQL (including Percona Distribution for PostgreSQL), MongoDB (including Percona Server for MongoDB), Amazon RDS, Amazon Aurora, Proxy SQL, and Percona XtraDB Cluster.&lt;/p>
&lt;p>Debian, Ubuntu, and Red Hat (AlmaLinux, Oracle Linux or Rocky Linux may also work) are supported. If you try installing on another distribution, might get the following message when trying to activate &lt;code>ps80&lt;/code>, &lt;code>pdps-8.0&lt;/code> or &lt;code>pdpxc-8.0&lt;/code> repositories:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo percona-release setup ps80
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Specified repository is not supported &lt;span class="k">for&lt;/span> current operating system!&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Meaning your OS is not supported yet.&lt;/p>
&lt;p>While PMM, both the server and the client, can be installed on most operating systems, if you want to set up MySQL on an OS that is not supported, you must consider configuring a virtual machine for Percona Server for MySQL.&lt;/p>
&lt;p>Check the documentation for more details about &lt;a href="https://docs.percona.com/percona-software-repositories/repository-location" target="_blank" rel="noopener noreferrer">repositories&lt;/a> maintained by Percona and &lt;a href="https://www.percona.com/services/policies/percona-software-support-lifecycle" target="_blank" rel="noopener noreferrer">supported platforms&lt;/a>.&lt;/p>
&lt;p>You can find system requirements for PMM in the &lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/1.x/faq.html#what-are-the-minimum-system-requirements-for-pmm" target="_blank" rel="noopener noreferrer">Frequently Asked Questions&lt;/a>. PMM Server and PMM clients communicate through the ports specified in the &lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/1.x/glossary.terminology.html#ports" target="_blank" rel="noopener noreferrer">Terminology&lt;/a> section.&lt;/p>
&lt;p>Note: Instructions for installing PMM and Percona Server for MySQL, described in the following sections, are for Debian, Ubuntu and derivatives. For Red Hat and derivatives, check the &lt;a href="https://www.percona.com/software/pmm/quickstart" target="_blank" rel="noopener noreferrer">Quickstart&lt;/a> guide and &lt;a href="https://docs.percona.com/percona-server/latest/installation/yum_repo.html" target="_blank" rel="noopener noreferrer">Installing Percona Server for MySQL on Red Hat Enterprise Linux and CentOS&lt;/a> from the documentation.&lt;/p>
&lt;h2 id="configuring-a-virtual-machine-for-mysql">Configuring a virtual machine for MySQL&lt;/h2>
&lt;p>If you’re on Linux and using a distribution that is not supported, configure a virtual machine before installing Percona Server for MySQL, otherwise continue with the “Installing and Configuring MySQL” section.&lt;/p>
&lt;h3 id="multipass">Multipass&lt;/h3>
&lt;p>&lt;a href="https://multipass.run/" target="_blank" rel="noopener noreferrer">Multipass&lt;/a> is an open source tool to generate cloud-style Ubuntu VMs quickly on Linux, macOS, and Windows.&lt;/p>
&lt;p>It gives you a simple but powerful CLI that allows you to quickly access an Ubuntu command line or create your own local mini-cloud.
On Linux, Multipass must be installed through a snap package. If Snap is not installed on your system, check the documentation for instructions on &lt;a href="https://snapcraft.io/docs/installing-snapd" target="_blank" rel="noopener noreferrer">how to install&lt;/a>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo snap install multipass&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, create your virtual machine:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ multipass launch lts --name percona&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>By default, when running &lt;code>multipass launch lts –name percona&lt;/code>, Multipass will create a virtual machine with 1 GB of RAM and a 4.7 GB disk. A new installation of MySQL only uses 2.4 GB, along with the operating system. A VM created with default configuration of Multipass would be enough for running MySQL.&lt;/p>
&lt;p>If you need a virtual machine with more resources, you can create a custom one with desired memory, storage and CPUs.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ multipass launch lts --name percona --mem 2G --disk 10G --cpus &lt;span class="m">2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The previous command will create a VM with 2 GB of RAM, a 10 GB disk and 2 CPUs.&lt;/p>
&lt;p>Once your VM is created and launched, you can access it by running &lt;code>multipass shell percona&lt;/code>.&lt;/p>
&lt;p>No additional configuration is required. Ports will be open automatically, and you can connect to any service configured on your VM through the IP address assigned by Multipass.&lt;/p>
&lt;p>&lt;code>multipass info percona&lt;/code> will give you information about your VM, including IP address.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">Name: percona
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">State: Running
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IPv4: 10.203.227.64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Release: Ubuntu 20.04.4 LTS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Image hash: 692406940d6a &lt;span class="o">(&lt;/span>Ubuntu 20.04 LTS&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Load: 0.09 0.09 0.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disk usage: 2.3G out of 9.5G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Memory usage: 550.4M out of 1.9G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Mounts: –&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>10.203.227.64&lt;/code> is the IP address of your virtual machine. You will need this value to set up PMM for monitoring MySQL.&lt;/p>
&lt;p>Run &lt;code>ip route show&lt;/code> to know the IP address that the host is identified by when logging into the VM. You will see a line similar to this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">10.203.227.0/24 dev mpqemubr0 proto kernel scope link src 10.203.227.1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>10.203.227.1&lt;/code> is the IP address that Multipass uses to identify the host.&lt;/p>
&lt;p>Both host and virtual machine IP addresses are required for configuring PMM.&lt;/p>
&lt;p>Log into your virtual machine for continuing with installation of Percona Server for MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ multipass shell percona&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="installing-required-packages">Installing required packages&lt;/h2>
&lt;h3 id="install-curl-and-gnupg2">Install curl and gnupg2&lt;/h3>
&lt;p>Before installing PMM or Percona Server for MySQL, make sure &lt;code>curl&lt;/code> and &lt;code>gnupg2&lt;/code> are installed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo apt install -y curl gnupg2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="install-percona-release">Install percona-release&lt;/h3>
&lt;p>The &lt;a href="https://docs.percona.com/percona-software-repositories/percona-release.html" target="_blank" rel="noopener noreferrer">percona-release&lt;/a> configuration tool allows users to automatically configure which &lt;a href="https://docs.percona.com/percona-software-repositories/repository-location.html" target="_blank" rel="noopener noreferrer">Percona Software repositories&lt;/a> are enabled or disabled. It supports both apt and yum repositories. Percona Server for MySQL will be installed from the &lt;code>ps80&lt;/code> repository and &lt;code>percona-release&lt;/code> is necessary for activating this repository.&lt;/p>
&lt;p>A good resource to learn more about this tool is &lt;a href="https://www.percona.com/blog/2020/12/15/the-hidden-magic-of-configuring-percona-repositories-with-a-percona-release-package/" target="_blank" rel="noopener noreferrer">this article&lt;/a> from Percona blog.&lt;/p>
&lt;p>Get the repository packages:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ wget https://repo.percona.com/apt/percona-release_latest.&lt;span class="k">$(&lt;/span>lsb_release -sc&lt;span class="k">)&lt;/span>_all.deb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Install the downloaded package with dpkg:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo dpkg -i percona-release_latest.&lt;span class="k">$(&lt;/span>lsb_release -sc&lt;span class="k">)&lt;/span>_all.deb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="installing-and-configuring-mysql">Installing and Configuring MySQL&lt;/h2>
&lt;h3 id="install-percona-server-for-mysql">Install Percona Server for MySQL&lt;/h3>
&lt;p>Enable the &lt;code>ps80&lt;/code> repository:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo percona-release setup ps80&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Install &lt;code>percona-server-server&lt;/code>, the package that provides the Percona Server for MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo apt install percona-server-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After installation, confirm that the service is running. You can check the service status by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ service mysql status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If the server is running, you will get the following output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">● mysql.service - Percona Server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Loaded: loaded &lt;span class="o">(&lt;/span>/lib/systemd/system/mysql.service&lt;span class="p">;&lt;/span> enabled&lt;span class="p">;&lt;/span> vendor preset: enabled&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Active: active &lt;span class="o">(&lt;/span>running&lt;span class="o">)&lt;/span> since Mon 2022-08-01 08:20:59 CDT&lt;span class="p">;&lt;/span> 1h 20min ago
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Main PID: &lt;span class="m">15552&lt;/span> &lt;span class="o">(&lt;/span>mysqld&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Status: &lt;span class="s2">"Server is operational"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Tasks: &lt;span class="m">38&lt;/span> &lt;span class="o">(&lt;/span>limit: 2339&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Memory: 362.7M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CGroup: /system.slice/mysql.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─15552 /usr/sbin/mysqld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Aug &lt;span class="m">01&lt;/span> 08:20:57 percona systemd&lt;span class="o">[&lt;/span>1&lt;span class="o">]&lt;/span>: Starting Percona Server...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Aug &lt;span class="m">01&lt;/span> 08:20:59 percona systemd&lt;span class="o">[&lt;/span>1&lt;span class="o">]&lt;/span>: Started Percona Server.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Otherwise, start the server:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo service mysql start&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="install-mysql-shell">Install MySQL Shell&lt;/h3>
&lt;p>&lt;a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/" target="_blank" rel="noopener noreferrer">MySQL Shell&lt;/a> is an advanced client and code editor for MySQL. This document describes the core features of MySQL Shell. In addition to the provided SQL functionality, similar to &lt;code>mysql&lt;/code>, MySQL Shell provides scripting capabilities for JavaScript and Python and includes APIs for working with MySQL&lt;/p>
&lt;p>Install MySQL Shell by running:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo apt install percona-mysql-shell&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>MySQL Shell will be used for configuring PMM to monitor MySQL. When necessary, just log into MySQL Shell as root:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mysqlsh root@localhost&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It will ask you for the password you assigned to the root user during the installation of Percona Server for MySQL.&lt;/p>
&lt;h2 id="installing-and-configuring-pmm">Installing and Configuring PMM&lt;/h2>
&lt;p>PMM runs from a container, so Docker must be installed if not already on your system. Percona has an &lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/easy-install.html" target="_blank" rel="noopener noreferrer">easy-install&lt;/a> script that would install Docker and any other required packages, as well as installing PMM Server.&lt;/p>
&lt;p>The &lt;code>easy-install&lt;/code> script provided by Percona checks if Docker is already on your system, otherwise it uses the &lt;a href="https://get.docker.com/" target="_blank" rel="noopener noreferrer">get-docker&lt;/a> script that will create a &lt;code>docker.list&lt;/code> file inside the &lt;code>/etc/apt/sources.list.d&lt;/code> directory, containing the official repository, and it will install and configure Docker on your system.&lt;/p>
&lt;p>Run the following command to get PMM Server:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -fsSL https://www.percona.com/get/pmm | /bin/bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Install PMM client:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo apt install pmm2-client&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Connect client to server:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo pmm-admin config --server-insecure-tls --server-url&lt;span class="o">=&lt;/span>https://admin:&lt;password>@pmm.example.com&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replace &lt;code>&lt;password>&lt;/code> with default password (&lt;code>admin&lt;/code>) and &lt;code>pmm.example.com&lt;/code> with &lt;code>localhost&lt;/code>. Once you set up PMM and log into the dashboard from the browser, you will be required to change your password.&lt;/p>
&lt;p>Go to &lt;code>https://localhost&lt;/code> in the browser.&lt;/p>
&lt;p>Note: If you’re running MySQL from a virtual machine, log into your VM before running the following instructions.&lt;/p>
&lt;p>Log into MySQL Shell as root and change to SQL mode:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mysqlsh root@localhost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="se">\s&lt;/span>ql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a PMM user for monitoring MySQL:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pmm'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'localhost'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IDENTIFIED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pass'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MAX_USER_CONNECTIONS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PROCESS&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SUPER&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REPLICATION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CLIENT&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RELOAD&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BACKUP_ADMIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'pmm'&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">'localhost'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Replacing &lt;code>'pass'&lt;/code> with your desired password.&lt;/p>
&lt;p>Note: Replace &lt;code>'localhost'&lt;/code> with the IP address of the host, if you installed Percona Server for MySQL on a virtual machine.&lt;/p>
&lt;p>Register the server for monitoring:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo pmm-admin add mysql --username&lt;span class="o">=&lt;/span>pmm --password&lt;span class="o">=&lt;/span>&lt;password> --query-source&lt;span class="o">=&lt;/span>perfschema&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Where &lt;code>&lt;password>&lt;/code> is the password you assigned to the user created for monitoring MySQL.&lt;/p>
&lt;p>Note: if you installed Percona Server for MySQL on a virtual machine, replace above command as follows: &lt;code>sudo pmm-admin add mysql --username=pmm --password=&lt;password> --host &lt;virtual-machine-IP-address> --query-source=perfschema&lt;/code>.&lt;/p>
&lt;p>PMM is now configured and monitoring MySQL.&lt;/p></content:encoded><author>Mario García</author><category>Linux</category><category>PMM</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2022/8/pmm-dashboard_hu_f9790b2462a77b1f.jpg"/><media:content url="https://percona.community/blog/2022/8/pmm-dashboard_hu_60b4ee1edbf9d9df.jpg" medium="image"/></item><item><title>Rivers vs Axis</title><link>https://percona.community/blog/2022/07/18/rivers-vs-axis/</link><guid>https://percona.community/blog/2022/07/18/rivers-vs-axis/</guid><pubDate>Mon, 18 Jul 2022 00:00:00 UTC</pubDate><description>It’s hard to find at least two people who would format the same SQL query in the same way. Everyone has their own style and their own arguments. And everyone is absolutely sure that this is the only right way.</description><content:encoded>&lt;p>It’s hard to find at least two people who would format the same SQL query in the same way. Everyone has their own style and their own arguments. And everyone is absolutely sure that this is the only right way.&lt;/p>
&lt;p>That was to be expected, because of the declarative nature of SQL. In imperative program languages, we define and control the order of statements execution, and it affects how we format our code. But in SQL query we don’t know at all the order of execution in advance and it deprives us of an important reference point. As a result, we have a lot of formatting options.&lt;/p>
&lt;p>But almost all of these options have one common unpleasant detail. In typography, it is called a &lt;a href="https://en.wikipedia.org/wiki/River_%28typography%29" target="_blank" rel="noopener noreferrer">river&lt;/a> and it is considered to be bad typography. Let’s look at a simple query. The river (marked red) tore our query into two jagged parts and makes the code more difficult to read.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="537" height="310" sizes="(max-width: 537px) 100vw, 537px" srcset="https://percona.community/blog/2022/7/rivers_hu_4b82f19d74a8e30c.webp 480w, https://percona.community/blog/2022/7/rivers_hu_f06dd75ff645e787.webp 537w" src="https://percona.community/blog/2022/7/rivers_hu_f06dd75ff645e787.webp" alt="Rivers" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>But legendary Joe Celko in his book &lt;a href="https://www.amazon.com/Celkos-Programming-Kaufmann-Management-Systems/dp/0120887975" target="_blank" rel="noopener noreferrer">“Joe Celko’s SQL Programming Style”&lt;/a> sad: let’s turn our rivers into axis. Same query but with the axis instead the river:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="563" height="309" sizes="(max-width: 563px) 100vw, 563px" srcset="https://percona.community/blog/2022/7/axis_hu_c795a01f4aed4f3.webp 480w, https://percona.community/blog/2022/7/axis_hu_f7bfa3f928dd8fa2.webp 563w" src="https://percona.community/blog/2022/7/axis_hu_f7bfa3f928dd8fa2.webp" alt="Axis" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Let’s look at a more complicated query with subqueries:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1122" height="603" sizes="(max-width: 1122px) 100vw, 1122px" srcset="https://percona.community/blog/2022/7/axis-2_hu_2ee5c1ce1794ab77.webp 768w, https://percona.community/blog/2022/7/axis-2_hu_57923f1bfa30caa3.webp 480w, https://percona.community/blog/2022/7/axis-2_hu_e5e2eb83b085ee91.webp 1122w" src="https://percona.community/blog/2022/7/axis-2_hu_e5e2eb83b085ee91.webp" alt="Axis" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We immediately visually detect three axis and three corresponding queries. This allows us to quickly and easily find out what this query does.&lt;/p>
&lt;p>In this post I use my way to build an axis (maybe not the best), surely you can find other methods for this.&lt;/p>
&lt;p>PS&lt;/p>
&lt;p>There are hot &lt;a href="https://www.reddit.com/r/SQL/comments/sp2jav/how_do_you_format_your_sql_queries" target="_blank" rel="noopener noreferrer">Reddit discussion&lt;/a> about rivers vs axis.&lt;/p>
&lt;p>PPS&lt;/p>
&lt;p>&lt;a href="https://github.com/sqlfluff/sqlfluff" target="_blank" rel="noopener noreferrer">sqlfluff&lt;/a> is amazing tool for linting and formatting your SQL queries. Unfortunately sqlfluff recognizes queries with axis like bad queries and it’s very sad. Please vote to this issue &lt;a href="https://t.co/YArKsaqUaM" target="_blank" rel="noopener noreferrer">https://t.co/YArKsaqUaM&lt;/a>&lt;/p></content:encoded><author>Maksim Gramin</author><category>SQL</category><media:thumbnail url="https://percona.community/blog/2022/7/axis_hu_191a7adef9d142fb.jpg"/><media:content url="https://percona.community/blog/2022/7/axis_hu_5328d1620013bd7d.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.29.0 Preview Release</title><link>https://percona.community/blog/2022/07/12/preview-release/</link><guid>https://percona.community/blog/2022/07/12/preview-release/</guid><pubDate>Tue, 12 Jul 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.29.0 Preview Release Hello folks! Percona Monitoring and Management (PMM) 2.29.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-2290-preview-release">Percona Monitoring and Management 2.29.0 Preview Release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.29.0 is now available as a Preview Release.&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release Notes can be found in &lt;a href="https://pmm-doc-release-pr-811.onrender.com/release-notes/2.29.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="known-issues">Known issues&lt;/h3>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-10312" target="_blank" rel="noopener noreferrer">PMM-10312&lt;/a>: Metrics are not displayed on Experimental Overview and Summary dashboards&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker">Percona Monitoring and Management server docker&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.29.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.29.0 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-4028.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable percona testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;a href="http://percona-vm.s3.amazonaws.com/PMM2-Server-2.29.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.29.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>&lt;code>ami-0e68224439dd6f200&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Database as Code. Not only migrations</title><link>https://percona.community/blog/2022/06/24/database-as-code/</link><guid>https://percona.community/blog/2022/06/24/database-as-code/</guid><pubDate>Fri, 24 Jun 2022 00:00:00 UTC</pubDate><description>We are used to the Everything as Code things and we love it. But how about Database as Code? If you have just dropped your DB migrations scripts to your pipeline, then it’s great, but that’s where “Database as Code” is just getting started. In this post, I will share my view on “Database as Code” mantra.</description><content:encoded>&lt;p>We are used to the Everything as Code things and we love it. But how about Database as Code? If you have just dropped your DB migrations scripts to your pipeline, then it’s great, but that’s where “Database as Code” is just getting started. In this post, I will share my view on “Database as Code” mantra.&lt;/p>
&lt;h2 id="everything-as-code">Everything as Code&lt;/h2>
&lt;p>And we will start with “Everything as Code” things. “Everything as Code” is a philosophy,
where any IT area might be represented as a plain code. And we can work with it using standard tools and technologies (like code editors, control version systems, static analyzis etc.). And yes, indeed, there are many “Everything as Code” realizations for many areas. For example - Gitlab CI, Ansible, Markdown, etc.&lt;/p>
&lt;p>And we can get all the benefits of working with Code:&lt;/p>
&lt;ul>
&lt;li>You have no more Monstrous GUI - Where you are afraid to click something wrong&lt;/li>
&lt;li>You can use Full Version Control For all your changes&lt;/li>
&lt;li>You can put your Code to the CI/CD pipeline&lt;/li>
&lt;li>And everybody in your team is coding and using the same tools&lt;/li>
&lt;/ul>
&lt;h2 id="how-about-database-as-code">How about “Database as Code”?&lt;/h2>
&lt;p>It was a very exotic combination of words, but &lt;a href="https://twitter.com/tastapod" target="_blank" rel="noopener noreferrer">Dan North&lt;/a> in hist talk “&lt;a href="https://speakerdeck.com/tastapod/arent-we-forgetting-someone" target="_blank" rel="noopener noreferrer">Aren’t we forgetting someone?&lt;/a>” proposed four simple rules for treating a database like Code:&lt;/p>
&lt;ul>
&lt;li>All database changes are scripted and automated&lt;/li>
&lt;li>All database changes are under version control&lt;/li>
&lt;li>Ability to release on demand at any time&lt;/li>
&lt;li>AND DBA should be integrated with Dev and Ops people&lt;/li>
&lt;/ul>
&lt;p>It’s been six years, and nowadays, it’s hard to find a project that doesn’t follow these rules (at least the first three). There are a lot of different database migration tools and different ways to integrate it with your Version Control system and your CI/CD pipeline.
For example &lt;a href="https://github.com/flyway/flyway" target="_blank" rel="noopener noreferrer">Flyway&lt;/a>, &lt;a href="https://github.com/TryGhost/Ghost" target="_blank" rel="noopener noreferrer">Ghost&lt;/a>, &lt;a href="https://github.com/sqitchers/sqitch" target="_blank" rel="noopener noreferrer">Sqitch&lt;/a>, &lt;a href="https://github.com/skeema/skeema" target="_blank" rel="noopener noreferrer">Skeema&lt;/a> and ofcourse &lt;a href="https://github.com/liquibase/liquibase" target="_blank" rel="noopener noreferrer">Liquibase&lt;/a>.&lt;/p>
&lt;p>And it’s really great!&lt;/p>
&lt;h2 id="db-isnt-only-schema">DB isn’t only Schema&lt;/h2>
&lt;p>First of all, DB is Data and Queries to Data (DML for data and metadata)&lt;/p>
&lt;p>Also databases needs:&lt;/p>
&lt;ul>
&lt;li>Administration (like space managment and memory management)&lt;/li>
&lt;li>Monitoring (like metrics gathering and perfomance troubleshooting)&lt;/li>
&lt;li>Documentation&lt;/li>
&lt;li>and all this stuff
And there is a special Language for all these things.
And of course - it is SQL!&lt;/li>
&lt;/ul>
&lt;p>SQL is a universal language for data and metadata. And SQL first of all was made by humans for humans, not for machines.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="735" height="500" sizes="(max-width: 735px) 100vw, 735px" srcset="https://percona.community/blog/2022/6/sql_everywhere_hu_ebb43a3efb0df69e.webp 480w, https://percona.community/blog/2022/6/sql_everywhere_hu_fddb8bc37f2ec958.webp 735w" src="https://percona.community/blog/2022/6/sql_everywhere_hu_fddb8bc37f2ec958.webp" alt="What is dbt" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="sql-hell">SQL Hell&lt;/h2>
&lt;p>But on the other hand we have some problems with SQL, I call it “SQL Hell” (like “JAR hell” or “DLL hell”). Our SQL-queries scattered everywhere:&lt;/p>
&lt;ul>
&lt;li>Our applicatons generate tonns of Dynamic Queries&lt;/li>
&lt;li>Many Static Queries are injected directly into the code of another program
(like Java, Python or something else)&lt;/li>
&lt;li>or placed in configuration files (like YML, JSON or TOML)&lt;/li>
&lt;/ul>
&lt;p>And we can’t control, test and trust these tons of SQL.&lt;/p>
&lt;h2 id="keep-all-sql-as-code">Keep All SQL as Code&lt;/h2>
&lt;p>But how about dead simple idea - keep all your SQL-queries as normal code? Why not? And I have prepared some additional “Database as Code” rules:&lt;/p>
&lt;ul>
&lt;li>All changes and operations with the Database and all queries against the Database should be expressed as a plain Code. Not only DDL, DML and all other kinds of SQL - too&lt;/li>
&lt;li>Git (or anything else VCS) is a single source of truth for all your DB Code&lt;/li>
&lt;li>SQL actually is a main database language supported by almost all DBMS and storages&lt;/li>
&lt;li>Treat your SQL code (your SQL-queries) like a normal Code. SQL is a human-oriented computer language for your Data and your Database, is not a bytecode. It also needs static analysis, code review, tests and automation of it all in your CI/CD Pipeline&lt;/li>
&lt;/ul>
&lt;p>The full version of these rules is hosted on &lt;a href="github.com/mgramin/database-as-code">GitHub&lt;/a>. Please check it out. And I will be very grateful for the stars, PR’s, issues and any other feedback.&lt;/p>
&lt;h2 id="is-there-it-in-wild-life">Is There It in Wild Life?&lt;/h2>
&lt;p>But are there tools in real life that satisfy this rules? And our answer: “Yes, there are”.&lt;/p>
&lt;h2 id="data-building-tool">Data Building Tool&lt;/h2>
&lt;p>First of all is Data Building Tool, or simply &lt;a href="https://github.com/dbt-labs/dbt-core" target="_blank" rel="noopener noreferrer">dbt&lt;/a>. dbt is a tool for data transformation. And the main idea is very clear and very simple - you just drop sql-file with your select statements to yor repository, while dbt materializes these statements into tables and views. No boilerplate code, only SQL!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1920" height="811" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/6/what_is_dbt_hu_155343b1ac16cb17.webp 768w, https://percona.community/blog/2022/6/what_is_dbt_hu_78679df90e87eeb5.webp 480w, https://percona.community/blog/2022/6/what_is_dbt_hu_f76588f745e22104.webp 1400w" src="https://percona.community/blog/2022/6/what_is_dbt_hu_f76588f745e22104.webp" alt="What is dbt" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>dbt provide also:&lt;/p>
&lt;ul>
&lt;li>Testing framework for you queries&lt;/li>
&lt;li>Templating with &lt;a href="https://jinja.palletsprojects.com" target="_blank" rel="noopener noreferrer">Jinja&lt;/a>&lt;/li>
&lt;li>Relationships management between queries&lt;/li>
&lt;li>Relationships visualization&lt;/li>
&lt;/ul>
&lt;h2 id="data-maping">Data Maping&lt;/h2>
&lt;p>Another example is Data Maping. Data Maping tools are usually very sophisticated tools for extracting and mapping our Data to our Data structures. It usually results in SQL code generation and performance issues.&lt;/p>
&lt;p>But some tools provide to us a Query-first design. You just drop sql-file with your query to yor RepOsitory:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- :name find_user :one
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And use it from your application code:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">python&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">queries&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -> { 'user_id': 42, 'username': 'mcfunley' }&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>it’s very simple and predictable and no magic!&lt;/p>
&lt;p>There are a lot of Query-first libraries and frameworks for different languages (e.g. &lt;a href="https://github.com/krisajenkins/yesql" target="_blank" rel="noopener noreferrer">Yesql&lt;/a>, &lt;a href="https://github.com/layerware/hugsql" target="_blank" rel="noopener noreferrer">HugSQL&lt;/a>, &lt;a href="https://github.com/mcfunley/pugsql" target="_blank" rel="noopener noreferrer">PugSQL&lt;/a>, &lt;a href="https://github.com/cashapp/sqldelight" target="_blank" rel="noopener noreferrer">SQLDelight&lt;/a> etc).&lt;/p>
&lt;h2 id="project-malewicz">Project “Malewicz”&lt;/h2>
&lt;p>And finally, I would like to present my small experimental project based on the “Database as Code” ideas - &lt;a href="https://github.com/mgramin/malewicz" target="_blank" rel="noopener noreferrer">Malewicz&lt;/a>.&lt;/p>
&lt;p>Malewicz is Yet Another graphical SQL-client (or SQL-manager)
for DB schema exploring and performance analysis but with some key features:&lt;/p>
&lt;ul>
&lt;li>This tool was originally designed for hacking and extending&lt;/li>
&lt;li>And you can use for that only your SQL skills (and a little bit HTML) without any boilerplate code.&lt;/li>
&lt;/ul>
&lt;p>Check it out on &lt;a href="https://github.com/mgramin/malewicz" target="_blank" rel="noopener noreferrer">GitHub&lt;/a> and try &lt;a href="http://malewicz.herokuapp.com" target="_blank" rel="noopener noreferrer">online demo&lt;/a>!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In the database world, we already have a great universal language with marvelous history, and it’s SQL. SQL allows you standardized declarative access not only to relational DB, but to non-relational DB, streaming platforms, files, clouds and so on. However, unfortunately, we often see that the SQL-code is often considered as a kind of &lt;a href="https://gramin.pro/posts/sql-is-not-a-bytecode-for-data" target="_blank" rel="noopener noreferrer">bytecode&lt;/a>. But we say - SQL-code is a &lt;em>normal&lt;/em> code and all SQL-code needs version control, review, testing, CI/CD and all this stuff.&lt;/p></content:encoded><author>Maksim Gramin</author><category>SQL</category><media:thumbnail url="https://percona.community/blog/2022/6/code_hu_f42eb76dc6469540.jpg"/><media:content url="https://percona.community/blog/2022/6/code_hu_a0035686ff226204.jpg" medium="image"/></item><item><title>Optimizing the Storage of Large Volumes of Metrics for a Long Time in VictoriaMetrics</title><link>https://percona.community/blog/2022/06/02/long-time-keeping-metrics-victoriametrics/</link><guid>https://percona.community/blog/2022/06/02/long-time-keeping-metrics-victoriametrics/</guid><pubDate>Thu, 02 Jun 2022 00:00:00 UTC</pubDate><description>Introduction Nowadays, the main tools for monitoring the operation of any application are metrics and logs. An important role is played by the time of their storage. Often, in order to understand certain processes and predict their development in the future, we need to analyze metrics over a fairly long period of time. In the case, when the project is just starting, their volume is relatively small, but over time it becomes larger and larger and there is a need for optimization. In this article, I will touch upon the mechanisms for processing, storing and optimizing metrics during their long-term storage.</description><content:encoded>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Nowadays, the main tools for monitoring the operation of any application are metrics and logs. An important role is played by the time of their storage. Often, in order to understand certain processes and predict their development in the future, we need to analyze metrics over a fairly long period of time. In the case, when the project is just starting, their volume is relatively small, but over time it becomes larger and larger and there is a need for optimization. In this article, I will touch upon the mechanisms for processing, storing and optimizing metrics during their long-term storage.&lt;/p>
&lt;h2 id="victoriametrics-at-a-glance">VictoriaMetrics at a Glance&lt;/h2>
&lt;p>The monitoring solution and the base for storing time series VictoriaMetrics was released relatively recently - in 2018, but has already gained popularity.&lt;/p>
&lt;p>Initially, VictoriaMetrics was designed as a time series database, but over time it has grown into a full-fledged alternative to Prometheus with its own ecosystem.&lt;/p>
&lt;p>VictoriaMetrics is currently a fast, cost-effective and scalable monitoring solution. You can deploy the application either from a binary file, a docker image or a snap package, or build it yourself from the source code. Single-node and cluster versions are available.&lt;/p>
&lt;h2 id="why-choose-victoriametrics">Why Choose VictoriaMetrics?&lt;/h2>
&lt;p>The main reasons for switching from Prometheus to VictoriaMetrics for us were significant savings in system requirements and the ability to work in Push mode.&lt;/p>
&lt;p>Despite many external tests, we wanted to get our own data. Test bench configuration had 8 CPU, 32 GB RAM, SSD drive. The test lasted 24 hours. 25 virtual machines were the source, each of which emulated 10 MySQL instances. In terms of the volume of metrics, there were to 96100 metrics per second, the total volume was about 8.5 billion metrics per day.&lt;/p>
&lt;p>The result of testing was about three times less disk space usage with VictoriaMetrics (8.44 GB against 23.11 with Prometheus), approximately twice less the amount of RAM. The CPU requirements were about the same.&lt;/p>
&lt;p>As for the push mode, it works as follows: exporters work on the target host and collect metrics, then in the classical scheme of work, Prometheus polls the exporters and collects metrics at specified intervals. This scheme of operation has a significant disadvantage as we need to maintain several ports open. The new scheme uses the VMAgent component, which is installed on the client side and collects metrics from exporters, after which it pushes to the VictoriaMetrics server.&lt;/p>
&lt;p>Also, important factors in favor of VictoriaMetrics were: ease of installation and subsequent support, the possibility of more flexible performance settings and the availability of features that are not available in other applications. For example, Prometheus lacks downsampling.&lt;/p>
&lt;h2 id="victoriametrics-single-node-and-cluster-versions">VictoriaMetrics Single-Node and Cluster Versions&lt;/h2>
&lt;p>VictoriaMetrics can work in two versions: single-node and cluster.&lt;/p>
&lt;p>The single-node version is used for relatively small amounts of data (less than a million metrics per second) and does not provide scalability and fault tolerance, since all application components are connected into a monolith.&lt;/p>
&lt;p>VictoriaMetrics consists of the following components:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>vmstorage&lt;/strong> - the storage itself;&lt;/li>
&lt;li>&lt;strong>vminsert&lt;/strong> - endpoint for receiving metrics based on the Prometheus remote_write API;&lt;/li>
&lt;li>&lt;strong>vmselect&lt;/strong> - a component that allows you to make queries using the Prometheus querying API.&lt;/li>
&lt;/ol>
&lt;p>In the official documentation, developers recommend using the single-node version and use clustered version only if there is a real need, and you understand the consequences of such a decision.&lt;/p>
&lt;h2 id="general-principles-of-tsdb-work">General Principles of TSDB Work&lt;/h2>
&lt;p>TSDB (a time series database) is used as a database for storing metrics. TSDB has many differences compared to relational databases - write operations prevail over read operations, there are no relationships between data. Since the metric has one value at a certain point in time, there is no need for nested structures. Typically, an amount of data is large.&lt;/p>
&lt;p>The unit of data in such a database is a time point. The data structure of such a point consists of:&lt;/p>
&lt;ol>
&lt;li>timestamp - time in Unix format&lt;/li>
&lt;li>The &lt;strong>name&lt;/strong> field, from which the name of the metric is taken. This field can be missing, but this is an antipattern, because in any case we need to know the name of the metric we are tracking.&lt;/li>
&lt;li>Additional Label fields that are needed for any actions with metrics (aggregation by some attribute, filtering, etc.)&lt;/li>
&lt;li>Field with metric value.&lt;/li>
&lt;/ol>
&lt;p>When data is recorded, time series are formed. A time series is a sequence of strictly monotonically increasing data points over time that can be accessed using a metric.&lt;/p>
&lt;p>Thus, we can say that the database is relatively “static”, because it contains a certain amount of data (metrics) that do not change over time. That means that, the data in these time series increases over time, but their number remains the same. This is the basis for optimization examples that will be discussed later.&lt;/p>
&lt;h2 id="optimization-of-large-queries">Optimization of Large Queries&lt;/h2>
&lt;p>If the number of metrics and their storage time increases, the amount of required resources for the application inevitably increases too. VictoriaMetrics has mechanisms for adjusting consumed resources.&lt;/p>
&lt;p>The &lt;code>memory.allowedPercent&lt;/code> and &lt;code>memory.allowedBytes&lt;/code> keys allow you to limit the amount of memory for external buffers and query caching data.&lt;/p>
&lt;p>The &lt;code>search.maxUniqueTimeseries&lt;/code> key prevents excessive resource consumption when executing large queries, and in some cases can prevent the application from crashing with an out of memory error when executing large queries. This parameter is set to 300000 by default and reflects the number of unique series returned in response to the request to &lt;code>/api/v1/query&lt;/code> and &lt;code>/api/v1/query_range&lt;/code> endpoints.&lt;/p>
&lt;p>Also, the &lt;code>search.maxSamplesPerQuery&lt;/code> key can be very useful, which limits the number of returned metrics in one query.&lt;/p>
&lt;p>The &lt;code>search.maxQueueDuration&lt;/code> key is responsible for the time to wait for a response to a request.&lt;/p>
&lt;p>In general, there are a fairly large number of keys that affect performance. In this post, I mention only whose that we use in our practice.&lt;/p>
&lt;h2 id="what-downsampling-is-and-how-it-works">What Downsampling Is and How It Works&lt;/h2>
&lt;p>An important feature of VictoriaMetrics is downsampling - the ability to delete data as it becomes obsolete. This functionality is available only in the Enterprise version. But it is also built into PMM - Percona Monitoring and Management.&lt;/p>
&lt;p>As I mentioned above (in the paragraph describing the features of the TSDB work), time series must have a large amount of data and be unchanged in order to obtain maximum sampling efficiency.&lt;/p>
&lt;p>The -downsampling.period key is responsible for the work. Example: the frequency of collecting metrics in our case is once every 5 seconds, but in this case, the volume of the database will grow very quickly if we have a large amount of metrics. So we define a policy for storing metrics - after one hour we store metrics with an interval of 10 seconds, every other day with an interval of 30 seconds, after a week - with an interval of 1 minute, after a month - with 5 minutes, after a year - 1 hour. So it will look like this:
&lt;code>-downsampling.period=1h:10s,1d:30s,1w:1m,30d:5m,360d:1h&lt;/code>&lt;/p>
&lt;h2 id="what-deduplication-is-and-how-it-works">What Deduplication Is and How It Works&lt;/h2>
&lt;p>Deduplication is a technology that allows you to analyze duplicate data and replace it with an appropriate reference. The use of deduplication can significantly reduce the amount of data. It is used when Prometheus or vmagent are working in HA mode and write to one VictoriaMetrics instance.&lt;/p>
&lt;p>In this case, we definitely need deduplication, since the database stores overlapping data, which significantly increases its volume and, in case of large volumes, the data request time. The &lt;code>dedup.minScrapeInterval&lt;/code> key is responsible for the operation of deduplication.&lt;/p>
&lt;p>For example, &lt;code>-dedup.minScrapeInterval=60s&lt;/code> means that within the same time series, all data will be collapsed and only the first point within 60 seconds will be saved. Since version 1.77 leave the last raw sample per each -dedup.minScrapeInterval discrete interval.&lt;/p>
&lt;p>It is recommended to set this parameter to scrape_interval for metrics. According to best practice, scrape_interval should be the same for all metrics, but this is a topic is for a separate post.&lt;/p>
&lt;h2 id="example-of-deduplication">Example of Deduplication&lt;/h2>
&lt;p>As an example, let’s consider the case where scrape_interval=10s and minScrapeInterval=15s.&lt;/p>
&lt;p>&lt;strong>Before deduplication:&lt;/strong> 05, 10, 15, 25, 35, 45, 55&lt;/p>
&lt;p>&lt;strong>interval:&lt;/strong> [00…15] [15…30] [30…45] [45…60]
&lt;strong>timestamp:&lt;/strong> [05 10] [15 25] [35 ] [45 55]&lt;/p>
&lt;p>Thus, after deduplication, only those points will remain: 05, 15, 35, 45.&lt;/p>
&lt;h2 id="rotation-of-metrics">Rotation of Metrics&lt;/h2>
&lt;p>Rotation of metrics is their removal when they become obsolete. The retentionPeriod key is responsible for rotation in the VM. By default, this period is 30 days. Therefore, you should immediately set the required storage period when launching VictoriaMetrics. Let’s dive a little deeper into the features of data storage. Example: we set the metrics rotation time to 1 year, 4 months, 2 weeks, 3 days and 5 hours &lt;code>-retentionPeriod=1.3y2w3d5h&lt;/code>. The year is a fractional number here to avoid the confusion with “m” which can mean both the month and the minute.&lt;/p>
&lt;p>When writing, data is stored in directories like ../data/{small,big}. This directory contains data like rowsCount_blocksCount_minTimestamp_maxTimestamp. The directories are rotated as follows: in the first unit of time of the selected period (day, week, month, year), metrics for the period preceding the previous one are deleted. Example: metric rotation is set to 1 month by default. On March 1, the directory containing the data for January is deleted. An important feature is that it is possible to increase the rotation time without any data loss of the running instance. If the rotation time is reduced accordingly, the data that goes beyond this time will be deleted.&lt;/p>
&lt;p>Based on personal experience, a sufficient data retention period for metrics will be one and a half years.&lt;/p>
&lt;p>It is also worth emphasizing that if you plan to store data indefinitely, then you still need to set the data retention period, in this case it is set to a very large number, for example, 900 years.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this post, we looked into the possibilities that VictoriaMetrics can offer to optimize the storage of metrics and reduce the usage of disk space and RAM. It can help you to reduce your costs significantly, especially if you need to store a lot of metrics. But be mindful regarding the parameters to be set, with clear understanding of your goals.&lt;/p></content:encoded><author>Anton Bystrov</author><category>blog</category><category>metrics</category><category>VictoriaMetrics</category><media:thumbnail url="https://percona.community/blog/2022/6/VictoriaMetrics_hu_935a9cc1c8f27e32.jpg"/><media:content url="https://percona.community/blog/2022/6/VictoriaMetrics_hu_239f0c8b38cab899.jpg" medium="image"/></item><item><title>Reduce Replication Lag</title><link>https://percona.community/blog/2022/06/01/speed-up-replication-lag/</link><guid>https://percona.community/blog/2022/06/01/speed-up-replication-lag/</guid><pubDate>Wed, 01 Jun 2022 00:00:00 UTC</pubDate><description>Replication Lag is just a fact of life with async-replication. We can’t stop lag, but we can help to reduce it. Many times the Seconds_Behind_Source can be very deciving, I have seen it go from 1 hour behind to 0 lag in the blink of an eye. There are many factors that can add to replica lag. Some of these are:</description><content:encoded>&lt;p>Replication Lag is just a fact of life with async-replication. We can’t stop lag, but we can help to reduce it. Many times the Seconds_Behind_Source can be very deciving, I have seen it go from 1 hour behind to 0 lag in the blink of an eye. There are many factors that can add to replica lag. Some of these are:&lt;/p>
&lt;ul>
&lt;li>Network IO&lt;/li>
&lt;li>Disk IO&lt;/li>
&lt;li>Database Workload&lt;/li>
&lt;li>Database settings&lt;/li>
&lt;/ul>
&lt;p>In this blog we will look a few database settings to help reduce lag. The settings we will look at are listed below.&lt;/p>
&lt;ol>
&lt;li>blinlog_transaction_dependency_tracking&lt;/li>
&lt;li>binlog_group_commit_sync_delay&lt;/li>
&lt;li>replica_parallel_type&lt;/li>
&lt;li>replica_parallel_workers&lt;/li>
&lt;/ol>
&lt;h3 id="hardware">Hardware:&lt;/h3>
&lt;ol>
&lt;li>Two Raspberry Pi 4 with 8GB of RAM.&lt;/li>
&lt;li>Sandisk 128GB Extreme microSDXC card.&lt;/li>
&lt;/ol>
&lt;h3 id="software">Software:&lt;/h3>
&lt;ol>
&lt;li>OS Raspbian Bullseye 64bit.&lt;/li>
&lt;li>Percona Server version 8.0.26.&lt;/li>
&lt;li>Sysbench 1.0.20.&lt;/li>
&lt;/ol>
&lt;h2 id="testing-setup">Testing Setup&lt;/h2>
&lt;p>Using Sysbench I set up 10 tables with 250,000 rows of data. If interested here is the command I used:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sysbench /usr/share/sysbench/oltp_read_write.lua \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--mysql-db=YOUR-DB --threads=4 --mysql-host=YOUR-HOST \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--mysql-user=YOUR-USER --mysql-password=YOUR-PASSWORD --tables=10 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--table-size=250000 prepare&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="test-one-with-default-settings">Test one with default settings&lt;/h2>
&lt;p>If you have not changed any of the default setting you can skip the blow changes. If your
not sure verify and change as needed.&lt;/p>
&lt;p>The initial testing was done with the following default settings. Make sure you make
these settings on both the primary and the replica. You will need to stop replication
on the replica before making the changes. Once changes are complete restart replication.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> mysql >set global binlog_transaction_dependency_tracking = 'COMMIT_ORDER';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global binlog_group_commit_sync_delay = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_type = 'DATABASE';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_workers = 0;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Using sysbench I ran the OLTP read/write test. I used the following setting for the test.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> sysbench --db-driver=mysql --report-interval=2 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --threads=4 --time=300 --mysql-host=YOUR-HOST \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --mysql-user=YOUR-USER --mysql-password=PASSWD \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --mysql-db=YOUR-DB /usr/share/sysbench/oltp_read_write.lua run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At the end of this test, replicacation lag was &lt;strong>20 minutes&lt;/strong> behind the primary.&lt;/p>
&lt;h2 id="test-two-with-adjusted-settings">Test two with adjusted settings&lt;/h2>
&lt;p>In the second test we will apply the new setting to help reduce replication lag time. Just like in test one
you will want to make these changes on both primary and replica. Make sure to stop replication on the replica
before applying the changes. Once changes are applied restart replication on the repliica.&lt;/p>
&lt;p>Make the following settings on your primary:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> mysql >set global binlog_transaction_dependency_tracking = 'writeset';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_type = 'LOGICAL_CLOCK';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_workers = 4;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Make the following changes on your replica:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">text&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> mysql >set global binlog_group_commit_sync_delay = 3000;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_type = 'LOGICAL_CLOCK';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql >set global replica_parallel_workers = 4;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I repeated the test from above. At the end of this test, replicacation lag was &lt;strong>6 minutes&lt;/strong> behind the primary.&lt;/p>
&lt;h2 id="blinlog_transaction_dependency_tracking--writeset">blinlog_transaction_dependency_tracking = writeset&lt;/h2>
&lt;p>I repeated the test from above. At the end of this test, replication lag was &lt;strong>6 minutes&lt;/strong> behind the primary.&lt;/p>
&lt;h2 id="setting-details">Setting Details:&lt;/h2>
&lt;h2 id="blinlog_transaction_dependency_tracking--writeset-1">blinlog_transaction_dependency_tracking = writeset&lt;/h2>
&lt;p>This allows for transactions that are marked as indipendent to be applied in parallel on the replica. Note to take advantage of
this you need to set replica_parallel_workers to a non 0 value.&lt;/p>
&lt;h2 id="binlog_group_commit_sync_delay--3000">binlog_group_commit_sync_delay = 3000&lt;/h2>
&lt;p>This controls how many microseconds the binary log commit waits before syncing the binlog to disk. Change this to a non 0 value
will enable more transactions to be synced at one time to disk. This will reduce the overall time to commit.&lt;/p>
&lt;h2 id="replica_parallel_type--logical_clock">replica_parallel_type = logical_clock&lt;/h2>
&lt;p>&lt;strong>As of version 8.0.27 the default value is logical_clock.&lt;/strong>
Transaction will be applied in parrallel on the replica based on the source timestamp in the binlog.&lt;/p>
&lt;h2 id="replica_parallel_workers--4">replica_parallel_workers = 4&lt;/h2>
&lt;p>&lt;strong>As of 8.0.27 this is a defaul value of 4.&lt;/strong>
This allows for multithreading on the replica, and set the number of applier threads.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>As we look at the results of both tests we saw a very big difference in lag. The Sysbench workload might not reflect a real world
database uses, but it does provide us with baseline numbers to compare.&lt;/p>
&lt;p>In the first test we saw a lag of 20 minutes at the end of the sysbench test. In our second test we say just 6 minutes of lag at
the end of the sysbench test.&lt;/p>
&lt;p>Dropping lag from 20 minutes down to 6 minutes is a decrease of more than &lt;strong>50%&lt;/strong>. That is a huge decrease.&lt;/p>
&lt;p>As I high lighted above two of these variables will become default values with 8.0.27. I did my testing on 8.0.26 so I could demo these changes
on a version what did not have the new standards.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Percona</category><category>MySQL</category><category>Replication</category><category>LAG</category><category>performance</category><media:thumbnail url="https://percona.community/blog/2022/6/snail_hu_71ed8a478bb0a978.jpg"/><media:content url="https://percona.community/blog/2022/6/snail_hu_e154e573474114d6.jpg" medium="image"/></item><item><title>How and Why Contribute to Communities</title><link>https://percona.community/blog/2022/05/30/csi-minikube-multinode/</link><guid>https://percona.community/blog/2022/05/30/csi-minikube-multinode/</guid><pubDate>Mon, 30 May 2022 00:00:00 UTC</pubDate><description>Why Lets start with a simple question “Why to contribute?”.</description><content:encoded>&lt;h2 id="why">Why&lt;/h2>
&lt;p>Lets start with a simple question “Why to contribute?”.&lt;/p>
&lt;p>In our day to day development’s and user’s life we use tons of OSS (open source) software. Ppl develop that software together to have ability to use them in more standard and open way, so they spend less time negotiating on interfaces and tools (and that is not the main reason, one of the reasons for OSS).&lt;/p>
&lt;p>As any sustainable process, OSS development also needs not only users but contributors to be able to move project forward as well as to sustain bugs, time and new tech trends. As users we have different use cases that might not be yet implemented but could be very valuable for other users.&lt;/p>
&lt;p>As an example I use &lt;code>minikube&lt;/code> for the development and testing of &lt;a href="https://docs.percona.com/percona-monitoring-and-management/using/dbaas.html" target="_blank" rel="noopener noreferrer">PMM DBaaS solution&lt;/a>. That tool allows me to run Kubernetes (k8s) locally and run &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">Percona operators&lt;/a> with help of DBaaS.&lt;/p>
&lt;p>One of the great &lt;code>minikube&lt;/code> features is to run real multi-node k8s clusters (see this &lt;a href="https://percona.community/blog/2021/12/20/pmm-minikube-postgres/" target="_blank" rel="noopener noreferrer">blog post&lt;/a> for details):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube start --nodes&lt;span class="o">=&lt;/span>&lt;span class="m">4&lt;/span> --cpus&lt;span class="o">=&lt;/span>&lt;span class="m">4&lt;/span> --memory&lt;span class="o">=&lt;/span>8G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube Ready control-plane,master 2d22h v1.22.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube-m02 Ready &lt;none> 2d22h v1.22.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube-m03 Ready &lt;none> 2d22h v1.22.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube-m04 Ready &lt;none> 2d22h v1.22.3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I usually run integration test with &lt;code>--driver=kvm&lt;/code> and some simple sanity tests with &lt;code>--driver=podman&lt;/code>.&lt;/p>
&lt;p>During my testing I found out that I can’t deploy operators with DBaaS on &lt;code>minikube&lt;/code> multi-node cluster and I found similar &lt;a href="https://perconadev.atlassian.net/browse/K8SPXC-879" target="_blank" rel="noopener noreferrer">Jira issue about it&lt;/a>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">console&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> kubectl get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">percona-server-mongodb-operator-fcc5c8d6-rphcs 1/1 Running 0 3h11m
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">percona-xtradb-cluster-operator-566848cf48-zm28g 1/1 Running 0 3h11m
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">pmm-0 1/1 Running 0 8m19s
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">test-haproxy-0 2/3 Running 0 9s
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">test-pxc-0 0/2 Init:CrashLoopBackOff 1 (5s ago) 9s
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="gp">$&lt;/span> kubectl logs test-pxc-0 pxc-init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">++ id -u
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">++ id -g
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">+ install -o 2 -g 2 -m 0755 -D /pxc-entrypoint.sh /var/lib/mysql/pxc-entrypoint.sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">install: cannot create regular file '/var/lib/mysql/pxc-entrypoint.sh': Permission denied
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So that is Why - ability to use &lt;code>minikube&lt;/code> to test operator’s DB deployments.&lt;/p>
&lt;h2 id="community-hackdays">Community Hackdays&lt;/h2>
&lt;p>Percona engineering management came with idea of dedicating a Focus day (we have those in Percona :) to community contributions. That was a great initiative, even if community contribution is our routine (we do it day to day when needed), having dedicated day is a nice way to educate others on how to do it on a good set of examples.&lt;/p>
&lt;p>I run with my &lt;code>minikube&lt;/code> multi-node issue as an example of both day to day work and what could be achieved during one community hackday.&lt;/p>
&lt;h3 id="day-to-day-community-hacking">Day to day community hacking&lt;/h3>
&lt;p>&lt;code>minikube&lt;/code> issue affects me as a developer so I spent a day to investigate it and half a day to find out workaround and next steps.&lt;/p>
&lt;p>First I spent quite a time to understand what is going on and if that issue of &lt;code>minikube&lt;/code> or DBaaS, or maybe operator’s issue. It was interesting detective work and I found out that it is indeed &lt;code>minikube&lt;/code> related issue and similar issue already exists in GitHub: &lt;a href="https://github.com/kubernetes/minikube/issues/12360" target="_blank" rel="noopener noreferrer">kubernetes/minikube #12360&lt;/a>.&lt;/p>
&lt;p>I have described my findings in &lt;a href="https://github.com/kubernetes/minikube/issues/12360#issuecomment-1123247475" target="_blank" rel="noopener noreferrer">this comment&lt;/a> and later found workaround that enables me and my colleagues to continue to use &lt;code>minikube&lt;/code> in &lt;a href="https://github.com/kubernetes/minikube/issues/12360#issuecomment-1123794143" target="_blank" rel="noopener noreferrer">multi-node setup&lt;/a>.&lt;/p>
&lt;p>That was day to day community hacking, I also spent a little time to find out how to fix it correctly and joined &lt;a href="https://minikube.sigs.k8s.io/docs/contrib/triage/" target="_blank" rel="noopener noreferrer">Minikube Triage party&lt;/a> to discuss the issue (sorry folks, still need to find time to join it regularly and help with triaging).&lt;/p>
&lt;p>And there I left it to the next opportunity to contribute.&lt;/p>
&lt;h3 id="hackday">Hackday&lt;/h3>
&lt;p>Opportunity presented itself quite quickly with new Community Hackday initiative and I decided that it would be a great time to fix part of the issue as the complete fix would take longer than a day.&lt;/p>
&lt;p>First step in fixing &lt;a href="https://github.com/kubernetes/minikube/issues/12360" target="_blank" rel="noopener noreferrer">kubernetes/minikube #12360&lt;/a> is to fix &lt;a href="https://github.com/kubernetes-csi/csi-driver-host-path" target="_blank" rel="noopener noreferrer">kubernetes-csi/csi-driver-host-path&lt;/a> to support unprivileged containers.&lt;/p>
&lt;p>So I took it for the day and here describe my progress…&lt;/p>
&lt;h2 id="contributing-to-the-community-project">Contributing to the community project&lt;/h2>
&lt;p>So your first help on how to contribute usually are &lt;a href="https://github.com/kubernetes-csi/csi-driver-host-path/blob/master/README.md" target="_blank" rel="noopener noreferrer">README.md&lt;/a> and &lt;a href="https://github.com/kubernetes-csi/csi-driver-host-path/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">CONTRIBUTING.md&lt;/a>.&lt;/p>
&lt;p>I started with forking the repo on GH (GitHub) UI and cloning it locally:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git clone git@github.com:denisok/csi-driver-host-path.git&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>First what I would like to do is to compile the code, create container and reproduce the issue.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> csi-driver-host-path
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make container
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./release-tools/verify-go-version.sh &lt;span class="s2">"go"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">======================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WARNING
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This projects is tested with Go v1.18.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Your current Go version is v1.16.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This may or may not be close enough.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> In particular test-gofmt and test-vendor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> are known to be sensitive to the version of
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Go.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">======================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># os_arch_seen captures all of the $os-$arch-$buildx_platform seen for the current binary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># that we want to build, if we've seen an $os-$arch-$buildx_platform before it means that&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># we don't need to build it again, this is done to avoid building&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># the windows binary multiple times (see the default value of $BUILD_PLATFORMS)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">os_arch_seen&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span> &lt;span class="o">&amp;&amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s1">''&lt;/span> &lt;span class="p">|&lt;/span> tr &lt;span class="s1">';'&lt;/span> &lt;span class="s1">'\n'&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="k">while&lt;/span> &lt;span class="nb">read&lt;/span> -r os arch buildx_platform suffix base_image addon_image&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">os_arch_seen_pre&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">os_arch_seen&lt;/span>&lt;span class="p">%%&lt;/span>&lt;span class="nv">$os&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nv">$arch&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nv">$buildx_platform&lt;/span>&lt;span class="p">*&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="k">if&lt;/span> ! &lt;span class="o">[&lt;/span> &lt;span class="si">${#&lt;/span>&lt;span class="nv">os_arch_seen_pre&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="si">${#&lt;/span>&lt;span class="nv">os_arch_seen&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="k">fi&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="k">if&lt;/span> ! &lt;span class="o">(&lt;/span>&lt;span class="nb">set&lt;/span> -x&lt;span class="p">;&lt;/span> &lt;span class="nb">cd&lt;/span> ./cmd/hostpathplugin &lt;span class="o">&amp;&amp;&lt;/span> &lt;span class="nv">CGO_ENABLED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="nv">GOOS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="nv">$os&lt;/span>&lt;span class="s2">"&lt;/span> &lt;span class="nv">GOARCH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="nv">$arch&lt;/span>&lt;span class="s2">"&lt;/span> go build -a -ldflags &lt;span class="s1">' -X main.version=v1.8.0-6-g50b99a39 -extldflags "-static"'&lt;/span> -o &lt;span class="s2">"/home/dkondratenko/Workspace/github/csi-driver-host-path/bin/hostpathplugin&lt;/span>&lt;span class="nv">$suffix&lt;/span>&lt;span class="s2">"&lt;/span> .&lt;span class="o">)&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">"Building hostpathplugin for GOOS=&lt;/span>&lt;span class="nv">$os&lt;/span>&lt;span class="s2"> GOARCH=&lt;/span>&lt;span class="nv">$arch&lt;/span>&lt;span class="s2"> failed, see error(s) above."&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nb">exit&lt;/span> 1&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="k">fi&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">os_arch_seen&lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="s2">";&lt;/span>&lt;span class="nv">$os&lt;/span>&lt;span class="s2">-&lt;/span>&lt;span class="nv">$arch&lt;/span>&lt;span class="s2">-&lt;/span>&lt;span class="nv">$buildx_platform&lt;/span>&lt;span class="s2">"&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>&lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ &lt;span class="nb">cd&lt;/span> ./cmd/hostpathplugin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ &lt;span class="nv">CGO_ENABLED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ &lt;span class="nv">GOOS&lt;/span>&lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ &lt;span class="nv">GOARCH&lt;/span>&lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ go build -a -ldflags &lt;span class="s1">' -X main.version=v1.8.0-6-g50b99a39 -extldflags "-static"'&lt;/span> -o /home/dkondratenko/Workspace/github/csi-driver-host-path/bin/hostpathplugin .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker build -t hostpathplugin:latest -f Dockerfile --label &lt;span class="nv">revision&lt;/span>&lt;span class="o">=&lt;/span>v1.8.0-6-g50b99a39 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 1/7: FROM alpine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 2/7: LABEL &lt;span class="nv">maintainers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"Kubernetes Authors"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> Using cache 9172a5d022e2a2550bcb0f6f7faa0b6a2126dcf7c1a0266924f4989370fbf80e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> 9172a5d022e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 3/7: LABEL &lt;span class="nv">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"HostPath Driver"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> Using cache 532cdc0c943df037d70368de6b7e90adb39dda3c6f9d7645c7ca6a9bd8d50abd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> 532cdc0c943
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 4/7: ARG &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>./bin/hostpathplugin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> Using cache 762a2b09549d02f9cd3d1dd8220c1b6890ae48efc155ae7aff276ae53bf7836b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> 762a2b09549
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 5/7: RUN apk add util-linux coreutils &lt;span class="o">&amp;&amp;&lt;/span> apk update &lt;span class="o">&amp;&amp;&lt;/span> apk upgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> Using cache 4bd7cf3998cc06cfdc780d3abdf6cedc452170ad93cf46cd3f4d12a8f5f97f09
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> 4bd7cf3998c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 6/7: COPY &lt;span class="si">${&lt;/span>&lt;span class="nv">binary&lt;/span>&lt;span class="si">}&lt;/span> /hostpathplugin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> a8e75bbeab1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STEP 7/7: ENTRYPOINT &lt;span class="o">[&lt;/span>&lt;span class="s2">"/hostpathplugin"&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">COMMIT hostpathplugin:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--> b0014a637af
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Successfully tagged localhost/hostpathplugin:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">b0014a637af31632b48f39def813637ad0d83d11d008d5b89edb52f28498b805
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ podman images
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">REPOSITORY TAG IMAGE ID CREATED SIZE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;none> &lt;none> 1ec47f8d8558 &lt;span class="m">46&lt;/span> seconds ago 35.6 MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">localhost/hostpathplugin latest f36f889fb57b &lt;span class="m">2&lt;/span> minutes ago 35.6 MB&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It appears to be super easy, I had Go 1.18 and podman already setup on my machine.&lt;/p>
&lt;p>So I have an image and now need to reproduce the issue. I need k8s cluster, setup CSI driver and upload my custom container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube start --nodes&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> --cpus&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> --memory&lt;span class="o">=&lt;/span>2G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube addons disable storage-provisioner
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🌑 &lt;span class="s2">"The 'storage-provisioner' addon is disabled"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl delete storageclass standard
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">storageclass.storage.k8s.io &lt;span class="s2">"standard"&lt;/span> deleted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> deploy/kubernetes-distributed/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>kubernetes-distributed&lt;span class="o">]&lt;/span>$ ./deploy.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">applying RBAC rules
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/v3.1.0/deploy/kubernetes/rbac.yaml --output /tmp/tmp.yXGWmlOXv9/rbac.yaml --silent --location
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply --kustomize /tmp/tmp.yXGWmlOXv9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">serviceaccount/csi-provisioner created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">role.rbac.authorization.k8s.io/external-provisioner-cfg created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">clusterrole.rbac.authorization.k8s.io/external-provisioner-runner created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rolebinding.rbac.authorization.k8s.io/csi-provisioner-role-cfg created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">clusterrolebinding.rbac.authorization.k8s.io/csi-provisioner-role created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">csistoragecapacities.v1beta1.storage.k8s.io:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> No resources found in default namespace.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deploying with CSIStorageCapacity v1beta1: &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deploying hostpath components
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./hostpath/csi-hostpath-driverinfo.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">csidriver.storage.k8s.io/hostpath.csi.k8s.io created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./hostpath/csi-hostpath-plugin.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> using image: k8s.gcr.io/sig-storage/csi-provisioner:v3.1.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> using image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.5.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> using image: k8s.gcr.io/sig-storage/hostpathplugin:v1.7.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> using image: k8s.gcr.io/sig-storage/livenessprobe:v2.6.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">daemonset.apps/csi-hostpathplugin created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./hostpath/csi-hostpath-storageclass-fast.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">storageclass.storage.k8s.io/csi-hostpath-fast created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./hostpath/csi-hostpath-storageclass-slow.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">storageclass.storage.k8s.io/csi-hostpath-slow created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./hostpath/csi-hostpath-testing.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> using image: docker.io/alpine/socat:1.7.4.3-r0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/hostpath-service created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/csi-hostpath-socat created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl patch storageclass csi-hostpath-fast -p &lt;span class="s1">'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">storageclass.storage.k8s.io/csi-hostpath-fast patched&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>There I have k8s cluster with 2 nodes, disabled standard &lt;code>minikube&lt;/code> storage-provisioned (which doesn’t support multi-node) deleted &lt;code>storageclass&lt;/code> that was working with that storage-provisioner and setup CSI hostpathplugin. Also enabled &lt;code>default&lt;/code> flag on the &lt;code>storageclass&lt;/code> for hostpathplugin so it would provision PVCs for me.&lt;/p>
&lt;p>Lets create test manifest &lt;code>perm_test.yaml&lt;/code> to reproduce the issue:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">StatefulSet&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fsGroup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">65534&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsGroup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">65534&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsNonRoot&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsUser&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">65534&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">busybox&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"/bin/sh"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"-c"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> touch /mnt/perm_test/file_test &amp;&amp; echo passed &amp;&amp; sleep 3600 &amp;&amp; exit 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo failed
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> exit 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/mnt/perm_test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeClaimTemplates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">perm-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">accessModes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"ReadWriteOnce"&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1G&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And test it to see that we really have a problem with unprivileged container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl apply -f perm_test.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">statefulset.apps/perm-test created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl logs perm-test-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">touch: /mnt/perm_test/file_test: Permission denied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">failed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl get pods -o wide
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">csi-hostpath-socat-0 1/1 Running &lt;span class="m">0&lt;/span> 24h 10.244.1.13 minikube-m02 &lt;none> &lt;none>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">csi-hostpathplugin-fnhvr 4/4 Running &lt;span class="m">0&lt;/span> 2m27s 10.244.0.24 minikube &lt;none> &lt;none>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">csi-hostpathplugin-w5rxt 4/4 Running &lt;span class="m">0&lt;/span> 2m30s 10.244.1.55 minikube-m02 &lt;none> &lt;none>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">perm-test-0 0/1 Error &lt;span class="m">0&lt;/span> 2m18s 10.244.1.56 minikube-m02 &lt;none> &lt;none>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we put &lt;code>sleep 3600&lt;/code> before &lt;code>exit 1&lt;/code> we actually could jump into the container and inspect the permissions:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">exec&lt;/span> --stdin --tty perm-test-0 -- sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">uid&lt;/span>&lt;span class="o">=&lt;/span>65534&lt;span class="o">(&lt;/span>nobody&lt;span class="o">)&lt;/span> &lt;span class="nv">gid&lt;/span>&lt;span class="o">=&lt;/span>65534&lt;span class="o">(&lt;/span>nobody&lt;span class="o">)&lt;/span> &lt;span class="nv">groups&lt;/span>&lt;span class="o">=&lt;/span>65534&lt;span class="o">(&lt;/span>nobody&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ stat /mnt/perm_test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">File: /mnt/perm_test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Size: &lt;span class="m">40&lt;/span> Blocks: &lt;span class="m">0&lt;/span> IO Block: &lt;span class="m">4096&lt;/span> directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Device: 10h/16d Inode: &lt;span class="m">82570&lt;/span> Links: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Access: &lt;span class="o">(&lt;/span>0755/drwxr-xr-x&lt;span class="o">)&lt;/span> Uid: &lt;span class="o">(&lt;/span> 0/ root&lt;span class="o">)&lt;/span> Gid: &lt;span class="o">(&lt;/span> 0/ root&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Access: 2022-05-27 13:21:56.905860356 +0000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Modify: 2022-05-27 13:21:56.905860356 +0000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Change: 2022-05-27 13:21:56.905860356 +0000&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we see that directory has &lt;code>Access: (0755/drwxr-xr-x)&lt;/code> and when we would like to write to it we have not enough permissions for &lt;code>nobody&lt;/code> user and file creation fails. We also could see that there are couple of pods running for the CSI plugin that actually provision PV/Cs.&lt;/p>
&lt;p>Clean up:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ kubectl delete -f perm_test.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl delete pvc perm-test-perm-test-0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I did code changes to add more logging to understand the program flow better and to see when the permissions would change if they actually.
During changes I learned a little bit on &lt;a href="https://github.com/google/glog#verbose-logging" target="_blank" rel="noopener noreferrer">glog&lt;/a> and that it has &lt;code>-v=5&lt;/code> in arguments for containers, so Info level by default.&lt;/p>
&lt;p>Lets create new image with those changes which we upload to the minikube and modify DeamonSet (csi driver):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make container
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rm hostpath.tar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ podman save --format docker-archive -o hostpath.tar localhost/hostpathplugin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 4fc242d58285 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 89f8b151f422 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 57a9469e70ba &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying config 29ba4a1533 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing manifest to image destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Storing signatures
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube image load ./hostpath.tar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube image ls
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker.io/localhost/hostpathplugin:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ kubectl &lt;span class="nb">set&lt;/span> image ds/csi-hostpathplugin &lt;span class="nv">hostpath&lt;/span>&lt;span class="o">=&lt;/span>localhost/hostpathplugin:latest&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Another way to modify DeamonSet is to run edit &lt;code>$ kubectl edit ds csi-hostpathplugin&lt;/code>, and change something. For example I was changing &lt;code>-v=6&lt;/code> and back to &lt;code>-v=5&lt;/code> so it would restart all containers with new image (that I uploaded).&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>stuck&lt;/strong>: I actually spent 2h trying to understand why I don’t see logs that I have added, and that actually led me to learn &lt;code>glog&lt;/code>, but it was quite simple. By default &lt;code>kubectl logs csi-hostpathplugin-w5rxt&lt;/code> shows logs for default container, not for hostpath. So I just needed to path right parameters &lt;code>kubectl logs csi-hostpathplugin-w5rxt -c hostpath&lt;/code>&lt;/p>&lt;/blockquote>
&lt;p>Adding volume to the pod happens in couple of stages, &lt;code>hostpath.go&lt;/code> creates directory on a needed node and &lt;code>nodeserver.go&lt;/code> publishes this volume to the pod by &lt;code>bind&lt;/code> mounting target pod &lt;code>mount&lt;/code> directory to the volume directory created by &lt;code>hostpath.go&lt;/code>.
Please check &lt;a href="https://github.com/container-storage-interface/spec/blob/master/spec.md" target="_blank" rel="noopener noreferrer">Spec&lt;/a>.&lt;/p>
&lt;p>It actually showed me that permission didn’t change from stage to stage but weren’t setup correctly on dir creation:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MountAccess&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MkdirAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mo">0777&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I have mode log before and after it, as it looked 0777 should be right one (allowing everyone to rwx on the directory):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">console&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="go">I0527 19:09:38.234437 1 hostpath.go:177] VolumePath: /csi-data-dir/8dc9889d-ddf0-11ec-b319-7e80679203b2 AccessType: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">I0528 07:07:57.543195 1 hostpath.go:187] mode info: -rwxr-xr-x for user: 0 group: 0
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So actually mode is 0755 instead of 0777 as requested in MkdirAll, and documentation clarifies:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">MkdirAll creates a directory named path, along with any necessary parents, and returns nil, or else returns an error.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The permission bits perm (before umask) are used for all directories that MkdirAll creates.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">If path is already a directory, MkdirAll does nothing and returns nil.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lets check umask for the root user (&lt;code>minikube ssh -n minikube-m02&lt;/code>):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">umask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">0022&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ getfacl --default /tmp/hostpath-provisioner/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">getfacl: Removing leading &lt;span class="s1">'/'&lt;/span> from absolute path names
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># file: tmp/hostpath-provisioner/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># owner: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># group: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ getfacl /tmp/hostpath-provisioner/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">getfacl: Removing leading &lt;span class="s1">'/'&lt;/span> from absolute path names
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># file: tmp/hostpath-provisioner/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># owner: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># group: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">user::rwx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">group::r-x
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">other::r-x&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>mkdir syscall actually accounts mask, which is 022. Or even mask is ignored as ACL from parent dir could be propagated:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://man7.org/linux/man-pages/man2/mkdir.2.html" target="_blank" rel="noopener noreferrer">https://man7.org/linux/man-pages/man2/mkdir.2.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://man7.org/linux/man-pages/man2/umask.2.html" target="_blank" rel="noopener noreferrer">https://man7.org/linux/man-pages/man2/umask.2.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>In my case there are no default ACLs but umask is set to 022 so: (0777 &amp; ~0022 &amp; 0777) actually gives us 0755.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">umask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">0022&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ getfacl --default /tmp/hostpath-provisioner/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">getfacl: Removing leading &lt;span class="s1">'/'&lt;/span> from absolute path names
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># file: tmp/hostpath-provisioner/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># owner: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># group: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ getfacl /tmp/hostpath-provisioner/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">getfacl: Removing leading &lt;span class="s1">'/'&lt;/span> from absolute path names
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># file: tmp/hostpath-provisioner/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># owner: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># group: root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">user::rwx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">group::r-x
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">other::r-x&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So that was it, we need to get rid of a mask and proposed fix is:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Chmod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mo">0777&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">glog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">V&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Couldn't change volume permissions: %w"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Cleaned up once again, compiled, created and pushed container. Tested it - It works!&lt;/p>
&lt;p>I created the branch on my fork, pushed it to my repo and followed PR procedure to create &lt;a href="https://github.com/kubernetes-csi/csi-driver-host-path/pull/356" target="_blank" rel="noopener noreferrer">kubernetes-csi/csi-driver-host-path #356&lt;/a>.&lt;/p>
&lt;p>That was the end of my Hackday and one step in solving issue in more general way.&lt;/p>
&lt;h2 id="value">Value&lt;/h2>
&lt;p>The excersise has a lot of value for me and Percona. I learned a lot of new things about k8s PV/PVC provisioning and CSI. For Percona we enabled development (devs and ci/cd) to run deployments on multi-node k8s local clusters.&lt;/p>
&lt;p>And hopefully for everyone else who needs to run unprivilege containers in multi-node with PVC.&lt;/p>
&lt;p>All together ppl developing OSS projects to benefit from each other and use better inovating Open-Source Software as well as to have a lot of fun :) .&lt;/p></content:encoded><author>Denys Kondratenko</author><category>PMM</category><category>Minikube</category><category>CSI</category><category>Kubernetes</category><category>k8s</category><category>Operator</category><media:thumbnail url="https://percona.community/blog/2022/5/how_and_why_contirbute_hu_93f4f14383ebc0a8.jpg"/><media:content url="https://percona.community/blog/2022/5/how_and_why_contirbute_hu_daebc0884a499935.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.28.0 Preview Release</title><link>https://percona.community/blog/2022/05/05/preview-release/</link><guid>https://percona.community/blog/2022/05/05/preview-release/</guid><pubDate>Thu, 05 May 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.28.0 Preview Release Hello folks! Percona Monitoring and Management (PMM) 2.28.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-2280-preview-release">Percona Monitoring and Management 2.28.0 Preview Release&lt;/h2>
&lt;p>Hello folks! Percona Monitoring and Management (PMM) 2.28.0 is now available as a Preview Release.&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release Notes can be found in &lt;a href="https://pmm-doc-release-pr-781.onrender.com/release-notes/2.28.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-server-docker">Percona Monitoring and Management server docker&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.28.0-rc&lt;/code>&lt;/p>
&lt;h3 id="percona-monitoring-and-management-client-package-installation">Percona Monitoring and Management client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.28.0 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3776.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable percona testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3.amazonaws.com/PMM2-Server-2.28.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.28.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://docs.percona.com/percona-monitoring-and-management/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;code>ami-09ce0dc58b2f81889&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>The MySQL Workshop Book Review</title><link>https://percona.community/blog/2022/05/03/the-mysql-workshop-book-review/</link><guid>https://percona.community/blog/2022/05/03/the-mysql-workshop-book-review/</guid><pubDate>Tue, 03 May 2022 00:00:00 UTC</pubDate><description>Good books on MySQL for beginners are rare and excellent ones are even rarer. I often get requests from novices starting with MySQL or intermediates looking to level up on recommendations on books targeted at their level. The MySQL Workshop (Amazon link) by Thomas Pettit and Scott Cosentino is a must buy for those two groups, or those of us who would like a handy reference.</description><content:encoded>&lt;p>Good books on MySQL for beginners are rare and excellent ones are even rarer. I often get requests from novices starting with MySQL or intermediates looking to level up on recommendations on books targeted at their level. The MySQL Workshop (&lt;a href="https://www.amazon.com/MySQL-Workshop-Interactive-Approach-Learning-ebook/dp/B084T32T3B/" target="_blank" rel="noopener noreferrer">Amazon link&lt;/a>) by Thomas Pettit and Scott Cosentino is a must buy for those two groups, or those of us who would like a handy reference.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="500" height="596" sizes="(max-width: 500px) 100vw, 500px" srcset="https://percona.community/blog/2022/5/TheMySQLWorkshopBook_hu_16bab17c21c38eec.webp 480w, https://percona.community/blog/2022/5/TheMySQLWorkshopBook_hu_3f4d3c04807a1b9d.webp 500w" src="https://percona.community/blog/2022/5/TheMySQLWorkshopBook_hu_3f4d3c04807a1b9d.webp" alt="The MySQL Workshop Book Review" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is a great book and I recommend getting a copy regardless of your MySQL expertise.&lt;/p>
&lt;h2 id="the-basics">The Basics&lt;/h2>
&lt;p>At seven hundred pages, this book has a wide scope that starts with background concepts like data normalization, proceeds into creating databases, SQL, and administration. And there are sections on programming with Node.js, working with Microsoft applications, loading data, DBA tasks, and logical backups. There are exercises at the end of the chapters with solutions at the end of the book.&lt;/p>
&lt;p>Writing such a book is a tremendous task and the authors need to be applauded as they have produced a great book.&lt;/p>
&lt;h2 id="the-nitty-gritty">The Nitty-Gritty&lt;/h2>
&lt;p>MySQL is a complex product and introducing concepts with a fresh approach is hard to do but this book does it consistently. Complex topics like creating functions are explained thoroughly without being bogged down in minute details.&lt;/p>
&lt;p>Does it cover everything? Nope, and no book under a few thousand pages will ever do that (while keeping pace with product development). There are minor omissions like constraint checks which a still fairly new but I would like to point you to the section on triggers that is the clearest explanation on the subject I have found.&lt;/p>
&lt;p>The writing style is concise, the formatting easy on the eyes, and I am sure the book will be very popular.&lt;/p></content:encoded><author>David Stokes</author><category>blog</category><category>books</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2022/5/TheMySQLWorkshop_hu_9d42aaa4854afa5a.jpg"/><media:content url="https://percona.community/blog/2022/5/TheMySQLWorkshop_hu_ddc11a064cd4a3d2.jpg" medium="image"/></item><item><title>Liquibase Data is Git for Databases</title><link>https://percona.community/blog/2022/04/25/liquibase-data-is-git-for-databases/</link><guid>https://percona.community/blog/2022/04/25/liquibase-data-is-git-for-databases/</guid><pubDate>Mon, 25 Apr 2022 00:00:00 UTC</pubDate><description>Author’s Note: Robert will be demoing Liquibase Data at Percona Live 2022 on Wednesday, May 18 at 11:50am. Add this presentation to your schedule.</description><content:encoded>&lt;p>&lt;em>Author’s Note: Robert will be demoing Liquibase Data at Percona Live 2022 on Wednesday, May 18 at 11:50am. &lt;a href="https://sched.co/10JOM" target="_blank" rel="noopener noreferrer">Add this presentation to your schedule.&lt;/a>&lt;/em>&lt;/p>
&lt;p>Git is an amazing tool for collaboration — developers can work together to build better software faster. However, the usual Git workflow neglects the database. With &lt;a href="https://github.com/liquibase/liquibase-data" target="_blank" rel="noopener noreferrer">Liquibase Data&lt;/a> we’re bringing git to the database so you can easily version containerized databases, share changes with team members, store versions in remote locations, and tag versions.&lt;/p>
&lt;h2 id="the-vanilla-git-workflow">The Vanilla Git Workflow&lt;/h2>
&lt;p>The standard Git workflow is simple. A developer can &lt;code>git init&lt;/code> to create a local repository. Next, after making changes, &lt;code>git commit&lt;/code> creates a local version. Then, the developer pushes to a remote branch using &lt;code>git push&lt;/code>. Finally, another developer can &lt;code>git pull&lt;/code> to see the new code updates.&lt;/p>
&lt;h2 id="liquibase-data-workflow">Liquibase Data Workflow&lt;/h2>
&lt;p>We created the same Git workflow in Liquibase Data. Using the &lt;a href="https://github.com/liquibase/liquibase-data" target="_blank" rel="noopener noreferrer">Liquibase Data extension&lt;/a>, Liquibase users can initialize a new database in a Docker container using &lt;code>liquibase data run&lt;/code>. Which databases? ALL of them. All it requires is a database Docker image that has a volume mount for the data. Liquibase takes it from there. If you already run your development databases via Docker, you will find that Liquibase Data parallels the &lt;code>docker run&lt;/code> command.&lt;/p>
&lt;p>Here’s what you’ll be able to do:&lt;/p>
&lt;ul>
&lt;li>Clone from remote repositories&lt;/li>
&lt;li>Make changes to the database&lt;/li>
&lt;li>Commit and push your changes to share with team members&lt;/li>
&lt;li>Tag commits&lt;/li>
&lt;li>Easily view the difference between two database commits to identify changes&lt;/li>
&lt;/ul>
&lt;p>Our team thinks this will be useful for test data management and supporting developer database workflows.&lt;/p>
&lt;p>Just like you commit after changing your code, you can do the same with Liquibase Data. After you add data to your database or change the schema, run &lt;code>liquibase data commit&lt;/code>. Commands such as &lt;code>push&lt;/code>, &lt;code>remote&lt;/code>, and &lt;code>log&lt;/code> are also available.&lt;/p>
&lt;h2 id="easily-compare-databases">Easily Compare Databases&lt;/h2>
&lt;p>Determining what has changed in your database schema can be very difficult. Liquibase Data makes it simple to find schema differences between commits using the &lt;code>diff&lt;/code> command. With Liquibase Data, the required database starts automatically for you to create the diff.&lt;/p>
&lt;h2 id="watch-liquibase-data-demos">Watch Liquibase Data Demos&lt;/h2>
&lt;p>Robert Reeves, CTO of Liquibase, &lt;a href="https://www.youtube.com/watch?v=k4m2UCqddHo" target="_blank" rel="noopener noreferrer">demonstrates how to quickly provision a developer instance of MongoDB&lt;/a>, make changes to MongoDB, and then commit the change. You’ll see how easy it is to roll your changes backward and forward.&lt;/p>
&lt;p>Check out our other Liquibase Data demos for &lt;a href="https://www.youtube.com/watch?v=AByPvVoWIXM" target="_blank" rel="noopener noreferrer">Oracle&lt;/a> and &lt;a href="https://www.youtube.com/watch?v=gLub_7Fcnh4" target="_blank" rel="noopener noreferrer">SQL Server&lt;/a>! Liquibase Data works with ANY database in a Docker Container.&lt;/p>
&lt;p>Try Liquibase Data
We think Liquibase Data will be helpful for developers sharing databases among team members. Just imagine — you’ll be able to share datasets you’re working on early in the process and share a separate one later in the process. The distribution of valid test data amongst Dev and QA will speed testing cycles and help find bugs sooner.&lt;/p>
&lt;p>Of course, we want to hear from you! Tell us what you would like to see in Liquibase Data and share with us how you are using it. Our &lt;a href="https://github.com/liquibase/liquibase-data/tree/main/beta" target="_blank" rel="noopener noreferrer">Open Beta program&lt;/a> is a great way to experience the benefits and give us input to make it work even better. We have a tutorial that will walk you through, step by step, how to use Liquibase Data. Along the way, you will have an opportunity to provide your thoughts.&lt;/p>
&lt;p>Finally, all of us at Liquibase thank you for your support over the past 15 years of open source greatness. We could not have done it with you. And, the best is yet to come!&lt;/p></content:encoded><author>Robert Reeves</author><category>blog</category><category>PerconaLive</category><category>PerconaLive2022</category><category>DevOps</category><media:thumbnail url="https://percona.community/blog/2022/4/liquibase-data-gitflow-580x296_hu_43885590fb372c6c.jpg"/><media:content url="https://percona.community/blog/2022/4/liquibase-data-gitflow-580x296_hu_4e632644633ce561.jpg" medium="image"/></item><item><title>A Quick Guide To Austin For Percona Live 2022 Attendees</title><link>https://percona.community/blog/2022/04/11/percona-live-austin-guide/</link><guid>https://percona.community/blog/2022/04/11/percona-live-austin-guide/</guid><pubDate>Mon, 11 Apr 2022 00:00:00 UTC</pubDate><description>Percona Live returns again to Austin May 16th through the 18th will find the city vibrant, charming, and weird. The semi-official motto for the city is ‘Keep Austin Weird’ and during your visit you will indeed see many of the residents working hard to do just that. Not in a bad way. Austin is at the intersection of so many cultural, artistic, and lifestyle modes that there are a fair amount of many different things happening at the same time to ensure that any dull moments you have will have to be an active choice on your part.</description><content:encoded>&lt;p>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live&lt;/a> returns again to Austin May 16th through the 18th will find the city vibrant, charming, and weird. The semi-official motto for the city is ‘Keep Austin Weird’ and during your visit you will indeed see many of the residents working hard to do just that. Not in a bad way. Austin is at the intersection of so many cultural, artistic, and lifestyle modes that there are a fair amount of many different things happening at the same time to ensure that any dull moments you have will have to be an active choice on your part.&lt;/p>
&lt;p>The following is a quick guide for those new to Austin or looking for activities for the days before or after the show&lt;/p>
&lt;h2 id="what-to-wear">What To Wear?&lt;/h2>
&lt;p>Austin in May averages 86F/30C (which is better than the August 96F/35C) so shorts, t-shirts, and comfortable shoes are a must. Bring sunscreen and water if you plan to spend time outdoors.&lt;/p>
&lt;h2 id="what-to-eat">What to Eat?&lt;/h2>
&lt;p>The two main choices are barbeque and Tex-Mex. But you will find any type of cuisine you desire either in the restaurants or the food trucks (mostly on South Congress Street but found throughout the city). You may see many celebrities but remember in Austin that Matthew McConaughey is just another professor at the University of Texas and that Elon Musk builds pickup trucks.&lt;/p>
&lt;h2 id="bbq">BBQ?&lt;/h2>
&lt;p>Barbecue is almost considered a religion in Texas and you will find many recommendations on where to go (see &lt;a href="https://austin.eater.com/maps/best-barbecue-austin-restaurants" target="_blank" rel="noopener noreferrer">https://austin.eater.com/maps/best-barbecue-austin-restaurants&lt;/a>) but everyone has their favorite. Major competitions are run each year to determine who is the best. My personal favorite is the Salt Lick has two locations that are sadly out of Austin proper and they are known for moderating the heat of their barbeque pits by using pecans which adds a unique flavor. And they do have a location at the airport too; the food is good but the ambience is lacking with all the flight announcements.&lt;/p>
&lt;p>But the other places are pretty good too. Stubb’s, Franklin, Black’s are all excellent. If you do see a line at another place where salivating people are somewhat impatiently waiting to order, then join the queue. And it is okay to salivate too.&lt;/p>
&lt;p>&lt;strong>Big hint:&lt;/strong> If you are not used to Texas Sweet Tea start with half sweetened and half unsweetened until your gums and your dentist have time to adjust.&lt;/p>
&lt;h2 id="tex-mex">Tex-Mex&lt;/h2>
&lt;p>This style of food is a tasty combination of Chihuahuan Mexican food and frontier based ingredients with lots of cheese and chili. What started as simple staple foods for settlers on the frontier made from the available commodities has evolved into a tasty treat.&lt;br>
Chuy’s original restaurant is a top pick and features theme rooms. Sadly we have already missed the birthday of Elvis Presley where all who dress like the King or his wife Priscilla dine free in their Elvis room. For the less decor oriented try Matt’s El Rancho.&lt;/p>
&lt;p>&lt;strong>Big tip:&lt;/strong> You will generally get big portions, especially if you order fajitas or margaritas.&lt;/p>
&lt;p>For breakfast, go to Snooze AM for the pineapple upside pancakes or an omelet.&lt;/p>
&lt;h2 id="museums">Museums&lt;/h2>
&lt;p>The Bob Bullock Museum is at the state capital building and is the state’s official history museum. The Museum of the Weird is just as the name implies and, while not official, provides a look into the odder parts of Austin. Not too far away is the Alamo in San Antonio (nearby the Alamo is the Buckhorn Saloon which actually has two floors of oddities that are weirder than the Museum of the Weird). The Museum of the Pacific War in Fredericksburg is a must for history fans. The Contemporary Austin is great for art fans while the Texas Toy museum will appeal to your inner child.&lt;/p>
&lt;h2 id="outdoors-activity">Outdoors Activity&lt;/h2>
&lt;p>Swim in spring fed Barton Springs, ride the bike trails, and hike your feet off before you rent a paddleboard to tour Lake Austin. Lots of things for the physically active with appealing hikes and you may actually run into an Armadillo.&lt;/p>
&lt;p>You can rent inner tubes to float the nearby Guadalupe or Comal Rivers. Rent another inner tube for your cooler of drinks. Or visit the Schlitterbahn water park. All three are a short drive away and worth an extra day on your trip for time to spend with family or friends.&lt;/p>
&lt;p>&lt;strong>Big hint:&lt;/strong> Stay hydrated as the heat is deceiving.&lt;/p>
&lt;h2 id="other-activities">Other Activities&lt;/h2>
&lt;p>Gruene (pronounced ‘green’) Hall is the oldest dancehall in Texas and is the place where many top stars got their starts. Currently there are no acts scheduled during the time of Percona Live (well, I expect they will be at Percona Live learning about databases!) but fans of ZZ Top, George Strait, Willy Nelson, or Greg Allman will relish the history of the place before heading to the Grist Mill for a meal. The dance hall itself has not changed much since being built in 1878 and they will open the side flaps when the dancers need fresh air.&lt;/p>
&lt;p>Sixth Street is the live music capital of Texas and you will find any genre there. This is where Stevie Ray Vaughn rose to fame and where Willie Nelson rebuilt his career after leaving Nashville.Ear plugs recommended but optional.&lt;/p>
&lt;p>Yes, the bats do fly out from under the Congress Street bridge at sunset which is amazing when millions of them fly out. The last physical Percona Live they were shy and only a few appeared. I assume that they were intimidated by having so many DBAs nearby.&lt;/p>
&lt;p>Austin is an awesome town and not just for Percona Live itself. I have only touched the proverbial iceberg tip on things to see and do there. If you have questions, find me at Percona Live or email me at &lt;a href="mailto:david.stokes@percona.com">david.stokes@percona.com&lt;/a> and hopefully we can try one of the local craft brews together.&lt;/p></content:encoded><author>David Stokes</author><category>blog</category><category>PerconaLive</category><category>PerconaLive2022</category><category>Conference</category><media:thumbnail url="https://percona.community/blog/2022/4/Guide-PL-2022_hu_e1bd6c54bf4e82b.jpg"/><media:content url="https://percona.community/blog/2022/4/Guide-PL-2022_hu_d41ab1d8990e6564.jpg" medium="image"/></item><item><title>Percona Monitoring and Management 2.27.0 Preview Release</title><link>https://percona.community/blog/2022/04/08/preview-release/</link><guid>https://percona.community/blog/2022/04/08/preview-release/</guid><pubDate>Fri, 08 Apr 2022 00:00:00 UTC</pubDate><description>Percona Monitoring and Management 2.27.0 Preview Release Percona Monitoring and Management 2.27.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="percona-monitoring-and-management-2270-preview-release">Percona Monitoring and Management 2.27.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.27.0 is now available as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments only&lt;/strong>, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Known issues:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PMM-9797" target="_blank" rel="noopener noreferrer">PMM-9797&lt;/a> - Wrong Plot on Stat Panels for DB Conns and Disk Reads at Home Dashboard&lt;/li>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PMM-9820" target="_blank" rel="noopener noreferrer">PMM-9820&lt;/a> - QAN page disappeared after upgrade via UI&lt;/li>
&lt;/ul>
&lt;p>Release Notes can be found &lt;a href="https://pmm-doc-release-pr-726.onrender.com/release-notes/2.27.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag:&lt;/p>
&lt;p>&lt;code>perconalab/pmm-server:2.27.0-rc&lt;/code>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.27.0 by this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3622.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable percona testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3.amazonaws.com/PMM2-Server-2.27.0.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2.27.0.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;code>ami-05592e370cca655b9&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us in &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">https://forums.percona.com/&lt;/a>.&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Raspberry Pi Bullseye Percona Server 64bit</title><link>https://percona.community/blog/2022/04/05/percona-server-raspberry-pi/</link><guid>https://percona.community/blog/2022/04/05/percona-server-raspberry-pi/</guid><pubDate>Tue, 05 Apr 2022 00:00:00 UTC</pubDate><description>I love the Raspberry Pi, and I love Percona server. The combination of the two can provide a nice home database. I have been running a Percona Server database since 2019 to hold all the weather information, that I collect from several of my Weather Stations.</description><content:encoded>&lt;p>I love the Raspberry Pi, and I love Percona server. The combination of the two can provide a nice home database. I have been running a Percona Server database since 2019 to hold all the weather information, that I collect from several of my Weather Stations.&lt;/p>
&lt;p>I did a my first blog post on installing Percona Server 5.7 on the Raspberry Pi 3+.&lt;/p>
&lt;p>You can read that blog post here:
&lt;a href="https://percona.community/blog/2019/08/01/how-to-build-a-percona-server-stack-on-a-raspberry-pi-3/" target="_blank" rel="noopener noreferrer">How to Build a Percona Server “Stack” on a Raspberry Pi 3+&lt;/a>&lt;/p>
&lt;p>Fast forward to 2022 and we now have the resources to build Percona Server 8.0 64-bit on the Raspberry Pi. In this post I will cover building and installing Percona Server 8.0.29 and Percona XtraBackup 8.0.29.&lt;/p>
&lt;p>Prereqs:&lt;/p>
&lt;ol>
&lt;li>Raspberry Pi 3B+, 4 or 400 (any memory size will work).&lt;/li>
&lt;li>128GB or 256GB microSD card. Of course you can go bigger.&lt;/li>
&lt;/ol>
&lt;p>When installing the Raspberry Pi OS on a Pi 4 or 400 make sure to choose the 64-bit image.
&lt;a href="https://raspberrytips.com/install-raspbian-raspberry-pi/" target="_blank" rel="noopener noreferrer">Install Raspberry Pi OS Bullseye on Raspberry Pi&lt;/a>&lt;/p>
&lt;h2 id="the-builds">The Builds&lt;/h2>
&lt;p>One step I found which will help to increase the speed and success of your build is to add a larger swap file.&lt;/p>
&lt;p>Create a new swap file. A 4GB swap file worked just fine. I created the swap file on the / partition.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo dd if=/dev/zero of=/swapfile4GB bs=1M count=4096
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mkswap /swapfile4GB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo swapon /swapfile4GB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod 0600 /swapfile4GB&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You will need to install these additional packages listed below:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo apt update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo apt upgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo apt install build-essential pkg-config cmake devscripts debconf debhelper automake bison ca-certificates \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">libcurl4-gnutls-dev libaio-dev libncurses-dev libssl-dev libtool libgcrypt20-dev zlib1g-dev lsb-release \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python3-docutils build-essential rsync libdbd-mysql-perl libnuma1 socat librtmp-dev libtinfo5 liblz4-tool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">liblz4-1 liblz4-dev libldap2-dev libsasl2-dev libsasl2-modules-gssapi-mit libkrb5-dev apt-get \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">libreadline-dev libudev-dev libev-dev libev4 libprocps-dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s download Percona Server and some additional tools.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ wget https://downloads.percona.com/downloads/Percona-Server-LATEST/Percona-Server-8.0.29-21/source/tarball/percona-server-8.0.29-21.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf percona-server-8.0.29-21.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf boost_1_77_0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://downloads.percona.com/downloads/Percona-XtraBackup-LATEST/Percona-XtraBackup-8.0.29-22/source/tarball/percona-xtrabackup-8.0.29-22.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf percona-xtrabackup-8.0.29-22.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="build-percona-server">Build Percona Server&lt;/h2>
&lt;p>At the time of writing 8.0.29-21 is the current version. If you have a USB 3 external drive, you might find the build will perform better from that device. In my build I used a 500GB SSD drive.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cd percona-server-8.0.29-21
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir arm64-build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd arm64-build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_BOOST=/home/pi/boost_1_77_0 -DCMAKE_INSTALL_PREFIX=/usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make -j2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With the 4GB swap file you created above you can use make -j2 for the compile. Depending on which Pi you are using build time should be around 3 hours.&lt;/p>
&lt;h2 id="build-xtrabackup">Build XtraBackup&lt;/h2>
&lt;p>At the time of writing 8.0.29-22 is the current version.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cd percona-xtrabackup-8.0.29-22
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir arm64-build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd arm64-build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_BOOST=$HOME/boost_1_77_0 -DCMAKE_INSTALL_PREFIX=/usr/local/xtrabackup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make -j3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The builds are now complete. Since we created everything from source they are a
few last things that need to be done.&lt;/p>
&lt;p>We need to create the mysql user and set its home directory. We need to update the /usr/local/mysql to be owned by mysql.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo useradd mysql -d /usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chown -R mysql:mysql /usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo mkdir -p /var/log/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chown -R mysql:mysql /var/log/mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>One last thing we need before to start MySQL for the 1st time is an /etc/my.cnf.&lt;/p>
&lt;p>Here is a sample you can work with.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo vi /etc/my.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Copy and paste the contents below into your my.cnf.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[mysqld]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">character-set-server = utf8mb4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">port = 3306
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">socket = /usr/local/mysql/mysql.sock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pid-file = /usr/local/mysql/mysqld.pid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">basedir = /usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">datadir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmpdir = /data0/mysql/tmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">general_log_file = /var/log/mysql/mysql-general.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log-error = /var/log/mysql/mysqld.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log_file =/var/log/mysql/slow_query.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log = 0 # Slow query log off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">expire_logs_days = 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log_error_verbosity = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lower_case_table_names = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_allowed_packet = 32M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_connections = 50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_user_connections = 40
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">skip-external-locking
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">skip-name-resolve
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table_open_cache=500
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">thread_cache_size=16
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">thread_pool_size=16
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_data_home_dir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_group_home_dir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_size = 2048M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_files_in_group = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_file_size = 128M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_buffer_size = 16M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_log_at_trx_commit = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_lock_wait_timeout = 50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_method = O_DIRECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_file_per_table = 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You will want to set the following setting to match your needs.&lt;/p>
&lt;ol>
&lt;li>datadir = /your/data/location/&lt;/li>
&lt;li>innodb_data_home_dir = &lt;strong>this should match your datadir&lt;/strong>&lt;/li>
&lt;li>innodb_log_group_home_dir = &lt;strong>this should match your datadir&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>Now you will want to create a mysqld.server service file in /lib/systemd/system&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo vi /lib/systemd/system/mysqld.service&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add the below contents to your mysqld.service.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[Unit]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Description=Percona Server 8.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">After=syslog.target
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">After=network.target
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[Install]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WantedBy=multi-user.target
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[Service]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">User=mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Group=mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">TimeoutSec=300
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WorkingDirectory=/usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Restart=on-failure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#RestartPreventExitStatus=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PrivateTmp=true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s setup Percona Server to stop and start with the OS.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sudo systemctl enable mysqld.Service&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="finish-your-build">Finish your build.&lt;/h2>
&lt;p>Once you have completed all the above steps. You can follow this blog post
&lt;a href="https://percona.community/blog/2021/09/06/lost-art-of-database-server-initialization/" target="_blank" rel="noopener noreferrer">The lost art of Database Server Initialization.&lt;/a>. Start at step 4.&lt;/p>
&lt;p>Thats it. You have a new Percona Server 8.0 running on your Raspberry Pi 4.&lt;/p>
&lt;p>This process does take some patience, but if you like the Raspberry Pi and&lt;/p>
&lt;p>Percona Server this is well worth the time it takes.&lt;/p>
&lt;h2 id="now-for-some-screen-shots">Now for some screen shots.&lt;/h2>
&lt;ul>
&lt;li>Percona Server:
&lt;figure>&lt;img width="817" height="230" sizes="(max-width: 817px) 100vw, 817px" srcset="https://percona.community/blog/2022/4/percona-systemctl-status_hu_f33492983575094b.webp 768w, https://percona.community/blog/2022/4/percona-systemctl-status_hu_d0426ae29122908f.webp 480w, https://percona.community/blog/2022/4/percona-systemctl-status_hu_c058170437429bf.webp 817w" src="https://percona.community/blog/2022/4/percona-systemctl-status_hu_c058170437429bf.webp" alt="Percona Status" loading="lazy" />&lt;/figure>&lt;/li>
&lt;li>Command Line Interface:
&lt;figure>&lt;img width="712" height="281" sizes="(max-width: 712px) 100vw, 712px" srcset="https://percona.community/blog/2022/4/percona-server-running_hu_622959205944e459.webp 480w, https://percona.community/blog/2022/4/percona-server-running_hu_e0b213dbf95e96e.webp 712w" src="https://percona.community/blog/2022/4/percona-server-running_hu_e0b213dbf95e96e.webp" alt="CLI Example" loading="lazy" />&lt;/figure>&lt;/li>
&lt;li>XtraBackup complete:
&lt;figure>&lt;img width="698" height="240" sizes="(max-width: 698px) 100vw, 698px" srcset="https://percona.community/blog/2022/4/percona-xtrabackup_hu_925924882ea10fd6.webp 480w, https://percona.community/blog/2022/4/percona-xtrabackup_hu_d2475f07bf245864.webp 698w" src="https://percona.community/blog/2022/4/percona-xtrabackup_hu_d2475f07bf245864.webp" alt="Complete Backup" loading="lazy" />&lt;/figure>&lt;/li>
&lt;/ul></content:encoded><author>Wayne Leutwyler</author><category>Percona</category><category>MySQL</category><category>64bit</category><category>Raspberry Pi</category><category>Bullseye</category><media:thumbnail url="https://percona.community/blog/2022/4/bullseye_hu_aa603fab7773c2f0.jpg"/><media:content url="https://percona.community/blog/2022/4/bullseye_hu_a6f9c8fad6527035.jpg" medium="image"/></item><item><title>The Ins and Outs of PostgreSQL Default Configuration Tuning</title><link>https://percona.community/blog/2022/03/31/the-ins-and-outs-of-postgresql-default-configuration-tuning/</link><guid>https://percona.community/blog/2022/03/31/the-ins-and-outs-of-postgresql-default-configuration-tuning/</guid><pubDate>Thu, 31 Mar 2022 00:00:00 UTC</pubDate><description>If you’re wondering what the optimal settings for a newly installed Postgres database are, here are some simple steps to take to tune it right from the start. Matt Yonkovit discussed them with Charly Batista, Postgres Tech Lead at Percona during the live-streamed meetup. Watch the recording to see how Charly tunes a default installation of Percona Distribution for PostgreSQL 13.</description><content:encoded>&lt;p>If you’re wondering what the optimal settings for a newly installed Postgres database are, here are some simple steps to take to tune it right from the start. Matt Yonkovit discussed them with Charly Batista, Postgres Tech Lead at Percona during the live-streamed meetup. Watch the &lt;a href="https://percona.community/events/percona-meetups/2022-01-27-percona-meetup-for-postgresql/" target="_blank" rel="noopener noreferrer">recording&lt;/a> to see how Charly tunes a default installation of Percona Distribution for PostgreSQL 13.&lt;/p>
&lt;p>Most of the default settings have been defined long, long time ago, where one gigabyte of RAM was very expensive. So, they are not optimal. There are lots of things that we can change, but let’s have a look at the basic things to make your box more reliable and raise both in speed and performance. They can be divided into 2 groups: OS settings (Linux kernel) and database settings.&lt;/p>
&lt;h2 id="os-linux-settings">OS (Linux) Settings&lt;/h2>
&lt;p>No matter that is your workload, these things you want to make sure you have set from the operating system perspective out of the gate to make your box healthier.&lt;/p>
&lt;h3 id="swap-and-swappiness">Swap and Swappiness&lt;/h3>
&lt;p>Allocate swap to prevent the kernel from killing the database. But keep in mind that swappines should not be too high. What is this swappiness? The swappiness tells the kernel how likely it should use the swap. Change it to 1 to allow the kernel to use the swap only when it is really necessary.
For swap, we need to create a file and then allocate it. For the swappiness, we can tell the systemctl to change the swappiness of our box.&lt;/p>
&lt;h3 id="transparent-huge-pages">Transparent Huge Pages&lt;/h3>
&lt;p>Transparent huge pages are enabled by default on the Linux kernel. And it’s not a good thing for databases like Postgres. They can cause a lot of memory fragmentation. It can slow down your database and also cause memory problems. For example, you need one gigabyte of memory for one activity, and even though you have one gigabyte available, they are split into small pieces. You cannot allocate that one gigabyte of memory. The first thing that the kernel will try to do is swap. It will just kill the database. So the transparent huge page can lead to performance issues, because we just don’t have memory, even though the memory is there, but the memory is not able to allocate.&lt;/p>
&lt;h3 id="cpu-speed">CPU Speed&lt;/h3>
&lt;p>Make sure the CPU runs at its max speed. Find the CPU Governor file and disable the on-demand utility. For the database, we don’t want to adjust on-demand, we always want it as fast as we can.&lt;/p>
&lt;h2 id="postgres-settings">Postgres Settings&lt;/h2>
&lt;p>Here are some database settings that you can change to optimize your database regardless of the workload you have.&lt;/p>
&lt;h3 id="shared-buffers-value">Shared Buffers Value&lt;/h3>
&lt;p>Change the value for shared buffers to 8 GB. You can ask - why? When we talk about MySQL, a good value for the shared buffer is 50% to 70% of your memory because that will give you the ability to grow. Typically, you want your hot data all in shared memory, all data that is access at a high frequency. But unlike MySQL, Postgres relies a lot on OS buffers. In case of Postgres, if you write intensive workload, it might want to get your shared buffer much smaller, like around 5% of memory that you have, because most of the things are going to go for the kernel buffer.&lt;/p>
&lt;h3 id="random-page-cost">Random Page Cost&lt;/h3>
&lt;p>Make sure you get the random page cost right. The random page cost is one that we think could be a big win for us, just because the default is so high compared to sequential. Lowering that by default is probably a good thing. Random page cost is the cost optimizer change. So, it’s going to push random pages to be a bit more costly and favor some sequential.&lt;/p>
&lt;p>We need to understand how Postgres stores data, and how Postgres stores the indexes. MySQL uses cluster storage here. The data that is stored on Postgres is not a cluster, it doesn’t organize the data. So it just keeps it. Random page cost is going to improve the index usage because it will prefer indexes. It changes the cost optimizer to prefer random pages or index scans over sequential. And it can improve or decrease performance a lot.&lt;/p>
&lt;p>Note that to be able to get the random page cost right, you need to understand what kind of disks you have. If you are using AWS, we suppose you have SSDs and NVMe. They are really fast. And the cost for the random page is almost the cost of the sequential page, which is why the change is not so high.&lt;/p>
&lt;h3 id="synchronous-commit">Synchronous Commit&lt;/h3>
&lt;p>So one thing that we can change on Postgres is the synchronous commit. The synchronous commit will force the database to commit every time to the cache, to the kernel, every time that you do a commit or transaction. It is a trade off that can improve performance. But you lose a little on reliability.&lt;/p>
&lt;p>But here is one setting on Postgres that you should never change even trying to improve performance - &lt;strong>fsync&lt;/strong>. Just never change it. By default, it is on, and it is on the top of the synchronous commit. The fsync instructs the kernel to write flash data to the disk for crash safety. If you disable the fsync, you might have some performance benefits, but your writes to the disk become not safe enough. You can have disk corruption. It’s really based on the disk having its own cache and its own systems going on, and you’re basically relying on it to do everything for you, instead of forcing that right to be consistent. It’s fine to work and tune and play around the synchronous commit, but not with the fsync.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Everything above are things that are independent of your workload. But there are no strict rules that you should, for example, use eight gigabytes of shared buffer if you have 32 gigabytes of memory. After you do all of those things, come back again, run the load test to check your performance. You can’t get worse performance instead of better performance.&lt;/p></content:encoded><author>Aleksandra Abramova</author><category>Postgres</category><category>PostgreSQL</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2022/3/Meetups-PG-1_hu_366faad9efd5803d.jpg"/><media:content url="https://percona.community/blog/2022/3/Meetups-PG-1_hu_4d03f38b7e3f8466.jpg" medium="image"/></item><item><title>How long do you keep the metrics in PMM?</title><link>https://percona.community/blog/2022/02/11/poll-metrics-keep/</link><guid>https://percona.community/blog/2022/02/11/poll-metrics-keep/</guid><pubDate>Fri, 11 Feb 2022 00:00:00 UTC</pubDate><description>Hello everyone! We are indeed excited to announce that the new release of VictoriaMetrics has many exciting features, one of them being downsampling.</description><content:encoded>&lt;p>Hello everyone! We are indeed excited to announce that the new release of VictoriaMetrics has many exciting features, one of them being &lt;a href="https://docs.victoriametrics.com/#downsampling" target="_blank" rel="noopener noreferrer">downsampling&lt;/a>.&lt;/p>
&lt;p>Downsampling helps to reduce disk space usage and improves query performance in a big and long time series if applied independently per each time series. However, this feature works only with a large number of samples per series.&lt;/p>
&lt;p>As we are keen on implementing downsampling in our future releases, we would like to understand how long you keep your metrics in PMM. Please go to the &lt;a href="https://forums.percona.com/t/how-long-do-you-keep-the-metrics-in-pmm/14236" target="_blank" rel="noopener noreferrer">Poll&lt;/a> page and provide your inputs.&lt;/p>
&lt;p>We appreciate your help. Thank You!&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Anton Bystrov</author><category>VictoriaMetrics</category><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>How to Publish a Blog Post</title><link>https://percona.community/blog/2022/02/10/how-to-publish-blog-post/</link><guid>https://percona.community/blog/2022/02/10/how-to-publish-blog-post/</guid><pubDate>Thu, 10 Feb 2022 00:00:00 UTC</pubDate><description>If you write technical content or just want to become an author, this blog is open to you! We accept any technical articles about databases and open source technologies. Also, we have no requirements for the uniqueness of the article. If your post is published on another resource, you can duplicate it here and get more attention.</description><content:encoded>&lt;p>If you write technical content or just want to become an author, this blog is open to you! We accept any technical articles about databases and open source technologies. Also, we have no requirements for the uniqueness of the article. If your post is published on another resource, you can duplicate it here and get more attention.&lt;/p>
&lt;p>In this post, I will explain step by step how to publish a post in our Community Blog. Following this guide step-by-step, everyone, even a non-technical person, will be able to publish the post. But if you face any issue on your way, just contact us at &lt;a href="#assistance-and-support">contact us&lt;/a>. Percona Community Team will be happy to help you!&lt;/p>
&lt;h2 id="preparing-the-environment-and-tools">Preparing the Environment and Tools.&lt;/h2>
&lt;p>This step is optional. It will allow you to check your post before publishing.&lt;/p>
&lt;p>We use the Hugo website engine. It turns Markdown pages into HTML very quickly and easily. We also use GitHub and GitHub Pages for free website hosting. So, learn how to use the Hugo engine by following the steps below.&lt;/p>
&lt;p>&lt;strong>Quick Steps&lt;/strong>&lt;/p>
&lt;p>I will briefly describe the steps for the professionals:&lt;/p>
&lt;ul>
&lt;li>Fork our repository &lt;a href="https://github.com/percona/community/" target="_blank" rel="noopener noreferrer">“percona/community”&lt;/a>.&lt;/li>
&lt;li>Make a Git Clone fork on your computer&lt;/li>
&lt;li>Install &lt;a href="https://gohugo.io/getting-started/installing/" target="_blank" rel="noopener noreferrer">Hugo engine&lt;/a>&lt;/li>
&lt;li>Run the Hugo server in the source code folder of the site with the command &lt;code>hugo server -D&lt;/code> and open a local copy of the site in your browser at &lt;code>localhost:1313&lt;/code>&lt;/li>
&lt;li>That’s it, you can change the texts and see the result immediately.&lt;/li>
&lt;li>Move on to the next step “How to add a post”.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Detailed Instructions&lt;/strong>&lt;/p>
&lt;p>Now let’s discuss these steps in detail.&lt;/p>
&lt;ol>
&lt;li>You need to make a fork of our &lt;a href="https://github.com/percona/community/" target="_blank" rel="noopener noreferrer">“percona/community”&lt;/a> repository with the source code of the site. Just open our repository and click the “Fork” button and follow the suggested steps. As a result, you will have a copy of the repository.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2044" height="1242" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_53d92d3f024771b4.webp 768w, https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_8e2077e4252feaae.webp 480w, https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_75b7bf7f6431b7ff.webp 1400w" src="https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_75b7bf7f6431b7ff.webp" alt="Fork" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>Git Clone your fork to your computer. Click Code to get the address to clone the repository. You can learn how to install and work with Git from the cool resource &lt;a href="https://githowto.com/" target="_blank" rel="noopener noreferrer">“Git How To”&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="868" height="644" sizes="(max-width: 868px) 100vw, 868px" srcset="https://percona.community/blog/2022/2/2-Git-Clone-Button_hu_8398e844abfe0d1.webp 768w, https://percona.community/blog/2022/2/2-Git-Clone-Button_hu_69d955d7f57b289.webp 480w, https://percona.community/blog/2022/2/2-Git-Clone-Button_hu_c2af72ba4381dcf7.webp 868w" src="https://percona.community/blog/2022/2/2-Git-Clone-Button_hu_c2af72ba4381dcf7.webp" alt="Clone" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Open the console on your computer and type the command:&lt;/p>
&lt;p>&lt;code>git clone git@github.com:dbazhenov/community.git percona-community&lt;/code>&lt;/p>
&lt;p>It is important to clone your fork and not the main repository. You will probably need to install Git on your computer if you didn’t do it before.&lt;/p>
&lt;p>When cloned, all of the Git repository code will be downloaded to your computer and you will be able to modify it and run it locally.&lt;/p>
&lt;p>You will see in the console:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Daniils-MacBook-Pro:Sites daniilbazhenov$ git clone git@github.com:dbazhenov/community.git percona-community
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Cloning into 'percona-community'...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Enumerating objects: 67797, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Counting objects: 100% (5879/5879), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Compressing objects: 100% (1346/1346), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remote: Total 67797 (delta 3005), reused 5847 (delta 2988), pack-reused 61918
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Receiving objects: 100% (67797/67797), 399.02 MiB | 843.00 KiB/s, done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Resolving deltas: 100% (33607/33607), done.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Updating files: 100% (1387/1387), done.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="3">
&lt;li>
&lt;p>Install the Hugo website engine: &lt;a href="https://gohugo.io/getting-started/installing/" target="_blank" rel="noopener noreferrer">Install Hugo&lt;/a>. The installation is not difficult. Follow the steps for your operating system and watch the official installation video. Hugo is a lightweight static website generator, it is free and open source software. You do not need to install a web server, programming language, or database.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Open the directory with the site code in the console, in my case it is: &lt;code>cd percona-community&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Launch the Hugo server with the command ‘hugo server -D’. When you start the server, the Hugo engine scans the structure of the project, generates the site on the fly and makes it available in the browser. You will see the URL as a result in the console.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Daniils-MacBook-Pro:percona-community daniilbazhenov$ hugo server -D
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Start building sites …
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | EN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-------------------+-------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pages | 1107
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Paginator pages | 23
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Non-page files | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Static files | 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Processed images | 1316
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Aliases | 64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Sitemaps | 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cleaned | 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Built in 99883 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Watching for changes in /percona-community/{archetypes,assets,content,layouts,static}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Watching for config changes in /percona-community/config.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Environment: "development"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Serving pages from memory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Press Ctrl+C to stop&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ol start="6">
&lt;li>Launch the website in your browser with the port indiicated in the console, for example &lt;code>http://localhost:1313/&lt;/code>. When you are done with the your post, you can stop the server using the buttons indicated in the console (Ctrl+C).&lt;/li>
&lt;/ol>
&lt;p>Now you can edit the site, add new posts and immediately see the result in your browser.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2742" height="1834" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/5-Browser-Test_hu_82293c26263052b5.webp 768w, https://percona.community/blog/2022/2/5-Browser-Test_hu_efe83643defb59c9.webp 480w, https://percona.community/blog/2022/2/5-Browser-Test_hu_640d4536793b46e3.webp 1400w" src="https://percona.community/blog/2022/2/5-Browser-Test_hu_640d4536793b46e3.webp" alt="Browser" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="how-to-publish-a-post">How to Publish a Post&lt;/h2>
&lt;p>I hope you have successfully made a fork and clone of the repository. Let’s start publishing your post.&lt;/p>
&lt;p>Our website works with text and posts marked up in &lt;a href="https://www.markdownguide.org/basic-syntax/" target="_blank" rel="noopener noreferrer">Markdown&lt;/a> syntax. You will probably need to edit the text a bit when you publish it.&lt;/p>
&lt;p>&lt;strong>Quick Steps&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Create a separate branch for your changes.&lt;/li>
&lt;li>Add information about you to the &lt;code>content/contributors/&lt;/code> folder.&lt;/li>
&lt;li>Add your photo to the folder &lt;code>assets/contributors&lt;/code>.&lt;/li>
&lt;li>Add your post in Markdown to the &lt;code>content/blog&lt;/code> directory.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Detailed Instructions&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Open the folder with the project source code in the console.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a separate git branch for your post.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;code>git checkout -b dbazhenov_post&lt;/code>&lt;/p>
&lt;ol start="3">
&lt;li>
&lt;p>Create a contributor card in the &lt;code>content/contributors/&lt;/code> folder.&lt;br>
You need to create a folder with your name and an index.md file.&lt;br>
You can find many examples in &lt;code>/content/contributors/&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add your photo to the folder &lt;code>assets/contributors&lt;/code>.&lt;br>
This is your avatar. Specify its address in your contributor profile file in the &lt;code>images&lt;/code> field.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If your post contains images, load the pre-made images into the &lt;code>assets/blog/[YEAR]/[month]&lt;/code> directory. If there is no directory, create one.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add the post in Markdown format to the &lt;code>content/blog&lt;/code> directory. Please, specify the name of your file according to the example: “Date-name-in-style-URL”. In my case, it is ‘2022-02-12-how-to-post.md’. If you are not familiar with Markdown, just have a look at other posts in the blog. There are examples for code blocks, headers, pictures and lists there.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Each post at the beginning must have special parameters in YAML format, the so-called &lt;a href="https://gohugo.io/content-management/front-matter/" target="_blank" rel="noopener noreferrer">Front Matter&lt;/a>. You can find an example in any of our 100+ blog posts. Specify these parameters: Title, Date, Draft status, Tags, Images (a special image that will be displayed in the list of posts and on social networks), Authors (your name as you are listed on the author card), Slug (optional, only if you want to have a special URL).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If you ran the Hugo server (&lt;code>hugo server -D&lt;/code>), you can open your post in your browser (&lt;code>localhost:1313&lt;/code>). If you have difficulties or problems, email us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> or open Issues on GitHub. To see the post in the list, you need to put the date earlier than today, as the list displays posts sorted by date.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2742" height="1834" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/5-Browser-Test_hu_82293c26263052b5.webp 768w, https://percona.community/blog/2022/2/5-Browser-Test_hu_efe83643defb59c9.webp 480w, https://percona.community/blog/2022/2/5-Browser-Test_hu_640d4536793b46e3.webp 1400w" src="https://percona.community/blog/2022/2/5-Browser-Test_hu_640d4536793b46e3.webp" alt="Browser Test" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You may also see errors in the console or browser. The most common errors are related to the image address.&lt;/p>
&lt;h2 id="saving-and-submitting-changes">Saving and Submitting Changes&lt;/h2>
&lt;p>&lt;strong>Quick Steps&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Run &lt;code>git status&lt;/code> to make sure you are in a separate branch and your changes are tracked with Git.&lt;/li>
&lt;li>Save the changes in Git and create a commit.&lt;/li>
&lt;li>Push changes to your repository on GitHub.&lt;/li>
&lt;li>Open your repository on GitHub and create a pull request.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Detailed Instructions&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Now we need to save the changes and submit them to GitHub. Make sure you are in a separate branch and your changes are tracked with git. Enter the command: &lt;code>git status&lt;/code>.
I see that I am on the dbazhenov_post branch and I have a new directory and a file.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1116" height="308" sizes="(max-width: 1116px) 100vw, 1116px" srcset="https://percona.community/blog/2022/2/6-git-status_hu_40670043a8f115f0.webp 768w, https://percona.community/blog/2022/2/6-git-status_hu_dcdef52997730179.webp 480w, https://percona.community/blog/2022/2/6-git-status_hu_ddb6483e02af9fb8.webp 1116w" src="https://percona.community/blog/2022/2/6-git-status_hu_ddb6483e02af9fb8.webp" alt="Git Status" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you are still on the main branch, create a new branch now &lt;code>git checkout -b "[branch_name]"&lt;/code>&lt;/p>
&lt;ol start="2">
&lt;li>Save the changes in Git and commit:&lt;/li>
&lt;/ol>
&lt;p>&lt;code>git add .&lt;/code>&lt;/p>
&lt;p>&lt;code>git commit -m "Blog: New Post by Daniil Bazhenov"&lt;/code>&lt;/p>
&lt;p>This way you will see all the modified or added files that will be sent to the remote repository.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1478" height="418" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/7-git-commit_hu_c6491172f65d0f70.webp 768w, https://percona.community/blog/2022/2/7-git-commit_hu_47f014d05eaf9a27.webp 480w, https://percona.community/blog/2022/2/7-git-commit_hu_99042e287e11e2b6.webp 1400w" src="https://percona.community/blog/2022/2/7-git-commit_hu_99042e287e11e2b6.webp" alt="Git Commit" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>
&lt;p>Submit changes to your repository: &lt;code>git push origin dbazhenov_post&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Open your repository on GitHub (fork). You will see that your branch is ready to be published (for creating a pull request).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2040" height="984" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/8-GitHub-Branch_hu_bb38ca05b38a7b2c.webp 768w, https://percona.community/blog/2022/2/8-GitHub-Branch_hu_76104f1edb0d5da2.webp 480w, https://percona.community/blog/2022/2/8-GitHub-Branch_hu_3362aaf4c8c6f477.webp 1400w" src="https://percona.community/blog/2022/2/8-GitHub-Branch_hu_3362aaf4c8c6f477.webp" alt="GitHub Branch" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="5">
&lt;li>Click the green &lt;em>Compare &amp; Pull Request&lt;/em> button. You will be directed to create a pull request to the main Percona Community repository. Complete the creation by clicking &lt;em>Create&lt;/em>.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2122" height="990" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_a0578e72f6ea3f80.webp 768w, https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_690e9a0516704add.webp 480w, https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_e74a4c1dbf66495a.webp 1400w" src="https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_e74a4c1dbf66495a.webp" alt="GitHub Branch" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>We will receive your pull request, check it and merge to our site. The post will be published after that. You can also check your pull request right in the GitHub interface under the Files tab.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1608" height="1240" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_f1800fe447f8a456.webp 768w, https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_998cceb155a6b392.webp 480w, https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_42d4dd91657cb529.webp 1400w" src="https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_42d4dd91657cb529.webp" alt="GitHub Check PR" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>To fix the mistake, simply make changes to the copy on your computer. Repeat steps 2 and 3 (git add, commit, push). Your new commit will automatically be added to your Pull Request.&lt;/p>
&lt;p>By publishing your post on our blog, you will become a full contributor to the repository and community. We will provide you with a special gift.&lt;/p>
&lt;h2 id="assistance-and-support">Assistance and Support&lt;/h2>
&lt;p>If you have any questions, please contact us:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/percona/community/issues" target="_blank" rel="noopener noreferrer">GitHub Issues&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">Forum&lt;/a>&lt;/li>
&lt;li>Email: &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>By the way, you can even just send us the text of the post and we will publish it ourselves for you!&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>blog</category><category>PMM</category><media:thumbnail url="https://percona.community/blog/2022/2/0-How-To-Post-Cover_hu_30b7356cd72f6656.jpg"/><media:content url="https://percona.community/blog/2022/2/0-How-To-Post-Cover_hu_98d720d05eb3d9bf.jpg" medium="image"/></item><item><title>5 Steps to Improve Performance of Default MySQL Installation</title><link>https://percona.community/blog/2022/01/27/5-steps-to-improve-performance-of-default-mysql-installation/</link><guid>https://percona.community/blog/2022/01/27/5-steps-to-improve-performance-of-default-mysql-installation/</guid><pubDate>Thu, 27 Jan 2022 00:00:00 UTC</pubDate><description>Let’s say you have a fresh MySQL installation. Are there any possible steps to improve performance right away? Yes, there are!</description><content:encoded>&lt;p>Let’s say you have a fresh MySQL installation. Are there any possible steps to improve performance right away? Yes, there are!&lt;/p>
&lt;p>Recently, Marcos Albe (Principal Support Engineer, Percona) did an &lt;a href="https://percona.community/events/percona-meetups/2022-01-14-percona-meetup-for-mysql-january-2022/" target="_blank" rel="noopener noreferrer">online tuning&lt;/a> on the MySQL Meetup hosted by Matt Yonkovit (Head of Open Source Strategy, Percona). Here are some steps you can consider to make your fresh MySQL installation to run better right from the start.&lt;/p>
&lt;p>So, we have a very basic default MySQL installation with some workload. It is connected to PMM, the slow query log is turned on. But it is largely unconfigured. Here is what actions Marcos considered to take to set up a new system to make sure that it’s actually set up from the beginning to reasonable defaults. Do some reactive configuration - go through the workload, observe bottlenecks and then configure to avoid bottlenecks&lt;/p>
&lt;p>&lt;strong>Step 1. Rate Limit for Slow Queries&lt;/strong>&lt;/p>
&lt;p>Go to MySQL Summary Dashboard In PMM, find your instance and set a rate limit instead of setting low query time to zero. Once we get to the 3000-4000 queries per second, these might start impacting performance in a way that is going to show in the latency papers. The thing is that while it is important to collect as many query details as possible, you don’t want to collect too many because it can impact performance and have the opposite effect of what you’re trying to do.&lt;/p>
&lt;p>&lt;strong>Step 2. Spikes&lt;/strong>&lt;/p>
&lt;p>Go down and turn the metrics off and on from data series from each of the graphs to be able to see the spikes on each magnitude. It allows you to find bad query patterns, under-dimensional or over-dimensional things.&lt;/p>
&lt;p>Think of it as workload being the light, MySQL being the prism and the metrics being the reflection of the light.&lt;/p>
&lt;p>Doing this, you can suggest different changes, like trying a slightly larger buffer size or increasing the thread cache. And as you go through the metrics, look at the values in the configuration, at the workload, and the actual work to find out if your hypotheses is correct.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2322" height="1200" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/1/move_hu_945fbde39f1bdd70.webp 768w, https://percona.community/blog/2022/1/move_hu_59eebe1209c420c9.webp 480w, https://percona.community/blog/2022/1/move_hu_a2f2ea63e3c064cf.webp 1400w" src="https://percona.community/blog/2022/1/move_hu_a2f2ea63e3c064cf.webp" alt="Spikes" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Step 3. Buffer Pool Size&lt;/strong>&lt;/p>
&lt;p>Increase the buffer pool size. It is probably the most used and most recommended setting.&lt;/p>
&lt;p>&lt;strong>Step 4. Redo Log Size&lt;/strong>&lt;/p>
&lt;p>Increase the redo log size and restart the instance. Make the redo log file as large as reasonably possible. What is reasonable? Reasonable is an amount of time that will allow you to write at that rate for the duration of your big workload. The purpose is to allow more pages during the heavy write periods. The only thing you could fear here is the recovery time. You should do some testing to see if the recovery times are acceptable, just like you do for backups. And then if the time of recovery is unacceptable, you should consider having a HA setup, semi-synchronous or virtually synchronous setup, where you can failover to the next instance when this one crashes. Also, you could get faster drives, or you could try to convince your developers to write less&lt;/p>
&lt;p>&lt;strong>Step 5. InnoDB IO Capacity&lt;/strong>&lt;/p>
&lt;p>Set InnoDB IO capacity to 200 unless you have proof you need more. Otherwise, you’re just forcing the flushing to happen too early. The thing is that you want to keep dirty pages. Dirty pages are the performance optimization. Imagine that you update the views counter for a popular video 100 times per second. If you have a very high capacity, you will probably write that road 50 times per second to disk. If you have a smaller capacity, you will probably write it once every few seconds. And then you’re actually only doing one write for hundreds of updates because all the rest were in memory and on the redo log.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2310" height="1174" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2022/1/innodb_hu_42aa06012e12b923.webp 768w, https://percona.community/blog/2022/1/innodb_hu_ee313008a4b82ad0.webp 480w, https://percona.community/blog/2022/1/innodb_hu_492995731f4c849a.webp 1400w" src="https://percona.community/blog/2022/1/innodb_hu_492995731f4c849a.webp" alt="InnoDB" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Conclusion&lt;/strong>&lt;/p>
&lt;p>If you want to watch the video of the meetup and see how Marcos tuned the installation, it is always available on the &lt;a href="https://percona.community/events/percona-meetups/2022-01-14-percona-meetup-for-mysql-january-2022/" target="_blank" rel="noopener noreferrer">Community Website&lt;/a>.
The meetups for MySQL, PostgreSQL, PMM, and MongoDB are regularly live-streamed. Stay tuned to &lt;a href="https://percona.community/events/percona-meetups/" target="_blank" rel="noopener noreferrer">announcements&lt;/a> and feel free to join!&lt;/p></content:encoded><category>MySQL</category><category>tuning</category><media:thumbnail url="https://percona.community/blog/2018/10/export-data-to-JSON-from-MySQL_hu_42c14ff7c0d70c61.jpg"/><media:content url="https://percona.community/blog/2018/10/export-data-to-JSON-from-MySQL_hu_db9c8048c3d6f089.jpg" medium="image"/></item><item><title>PMM 2.26.0 Preview Release</title><link>https://percona.community/blog/2022/01/27/preview-release-2-26/</link><guid>https://percona.community/blog/2022/01/27/preview-release-2-26/</guid><pubDate>Thu, 27 Jan 2022 00:00:00 UTC</pubDate><description>PMM 2.26.0 Preview Release Percona Monitoring and Management 2.26.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="pmm-2260-preview-release">PMM 2.26.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.26.0 is now available as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments&lt;/strong> only, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release Notes can be found &lt;a href="https://github.com/percona/pmm-doc/blob/bfc10bc70028af54e5f45a412010c3b301685750/docs/release-notes/2.26.0.md" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag: &lt;a href="https://hub.docker.com/layers/perconalab/pmm-server/2.26.0-rc/" target="_blank" rel="noopener noreferrer">perconalab/pmm-server:2.26.0-rc&lt;/a>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.26.0 from this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3413.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable original testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3-website-us-east-1.amazonaws.com/PMM2-Server-2022-01-27-1524.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2022-01-27-1524.ova&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Rasika Chivate</author><category>PMM</category><media:thumbnail url="https://percona.community/blog/2022/1/preview_226_hu_a4515b2b4583e574.jpg"/><media:content url="https://percona.community/blog/2022/1/preview_226_hu_48fb868323cf9c7d.jpg" medium="image"/></item><item><title>How to replace `docker` with `podman` for PMM development</title><link>https://percona.community/blog/2021/12/27/replace-docker-with-podman-for-pmm-dev/</link><guid>https://percona.community/blog/2021/12/27/replace-docker-with-podman-for-pmm-dev/</guid><pubDate>Mon, 27 Dec 2021 00:00:00 UTC</pubDate><description>What is Podman? Podman is a daemonless container engine for developing, managing, and running OCI (Open Container Initiative) Containers on your Linux System. Containers can either be run as root or in rootless mode. More details here.</description><content:encoded>&lt;p>What is &lt;a href="https://podman.io/" target="_blank" rel="noopener noreferrer">Podman&lt;/a>? Podman is a daemonless container engine for developing, managing, and running OCI (&lt;a href="https://opencontainers.org/" target="_blank" rel="noopener noreferrer">Open Container Initiative&lt;/a>) Containers on your Linux System. Containers can either be run as root or in rootless mode. More details &lt;a href="https://podman.io/whatis.html" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;p>Check out also &lt;a href="https://kubernetespodcast.com/episode/164-podman/" target="_blank" rel="noopener noreferrer">Kubernetes Podcast&lt;/a> to learn more about &lt;code>podman&lt;/code> and listen to it’s creators.&lt;/p>
&lt;p>Why to replace? Especially in development I need simplest possible solution, I don’t need additional daemon running or allowing something to run with elevated privileges. And also it much closer to my personal understanding how it should work to run containers.&lt;/p>
&lt;p>Looks like for Linux it is quite possible, but the experience could be different on MacOS or Windows.&lt;/p>
&lt;p>But what is described bellow is strictly for development, not intended for &lt;strong>production&lt;/strong>! &lt;strong>Podman currently is not supported for production for PMM&lt;/strong>.&lt;/p>
&lt;p>I would use Fedora 35 distro in examples bellow, first lets install &lt;code>podman&lt;/code> and start needed tools:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo dnf install podman docker-compose
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ systemctl --user start podman.socket&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>we still need &lt;code>docker-compose&lt;/code> as most of PMM tooling is built around it&lt;/li>
&lt;li>starting &lt;code>podman.socket&lt;/code> so compose would actually talk to &lt;code>podman&lt;/code> instead of &lt;code>docker&lt;/code> socket&lt;/li>
&lt;/ul>
&lt;h2 id="pmm-managed">pmm-managed&lt;/h2>
&lt;p>First lets try to compile and run &lt;code>pmm-managed&lt;/code>.&lt;/p>
&lt;h3 id="podmansocket">podman.socket&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/usr/lib/python3.10/site-packages/docker/transport/unixconn.py"&lt;/span>, line 30, in connect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sock.connect&lt;span class="o">(&lt;/span>self.unix_socket&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FileNotFoundError: &lt;span class="o">[&lt;/span>Errno 2&lt;span class="o">]&lt;/span> No such file or directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ systemctl --user status podman.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">● podman.socket - Podman API Socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Loaded: loaded &lt;span class="o">(&lt;/span>/usr/lib/systemd/user/podman.socket&lt;span class="p">;&lt;/span> disabled&lt;span class="p">;&lt;/span> vendor preset: disabled&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Active: active &lt;span class="o">(&lt;/span>listening&lt;span class="o">)&lt;/span> since Wed 2021-12-22 22:50:33 CET&lt;span class="p">;&lt;/span> 1h 12min ago
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Triggers: ● podman.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Docs: man:podman-system-service&lt;span class="o">(&lt;/span>1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Listen: /run/user/1000/podman/podman.sock &lt;span class="o">(&lt;/span>Stream&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/podman.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nv">DOCKER_HOST&lt;/span>&lt;span class="o">=&lt;/span>unix:///run/user/1000/podman/podman.sock make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="c1"># ^^^that or exporting env would get us to the next stage&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>docker-compose&lt;/code> that is used to bring up environment couldn’t connect to the docker daemon and thus failing. There is an env var to point to the right socket to talk to so lets find out the socket path and set it.&lt;/p>
&lt;p>Set that var in your environment (&lt;code>.bashrc&lt;/code> or similar) or I set it in the current session:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">export&lt;/span> &lt;span class="nv">DOCKER_HOST&lt;/span>&lt;span class="o">=&lt;/span>unix:///run/user/1000/podman/podman.sock&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="short-name-image-resolution">short-name image resolution&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Pulling pmm-managed-server ... error
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: &lt;span class="k">for&lt;/span> pmm-managed-server failed to resolve image name: short-name resolution enforced but cannot prompt without a TTY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: failed to resolve image name: short-name resolution enforced but cannot prompt without a TTY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nv">DOCKER_HOST&lt;/span>&lt;span class="o">=&lt;/span>unix:///run/podman/podman.sock &lt;span class="nv">PMM_SERVER_IMAGE&lt;/span>&lt;span class="o">=&lt;/span>docker.io/perconalab/pmm-server:dev-latest make env-up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now it has failed because the system doesn’t accept the short names for images, but there is another env for it &lt;code>PMM_SERVER_IMAGE&lt;/code>.&lt;/p>
&lt;p>The short image name resolution we could tune in the system, &lt;code>/etc/containers/registries.conf&lt;/code> says:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">For more information on this configuration file, see containers-registries.conf(5).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># We recommend always using fully qualified image names including the registry
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># server (full dns name), namespace, image name, and tag
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e.,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># quay.io/repository/name@digest) further eliminates the ambiguity of tags.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># When using short names, there is always an inherent risk that the image being
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># pulled could be spoofed. For example, a user wants to pull an image named
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># `foobar` from a registry and expects it to come from myregistry.com. If
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># myregistry.com is not first in the search list, an attacker could place a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># different `foobar` image at a registry earlier in the search list. The user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># would accidentally pull and run the attacker's image and code rather than the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># intended content. We recommend only adding registries which are completely
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># trusted (i.e., registries which don't allow unknown or anonymous users to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># create accounts with arbitrary names). This will prevent an image from being
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># spoofed, squatted or otherwise made insecure. If it is necessary to use one
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># of these registries, it should be added at the end of the list.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The way to go is to alias names for example in &lt;code>/etc/containers/registries.conf.d/001-shortnames-den.conf&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[aliases]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # docker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "perconalab/pmm-server" = "docker.io/perconalab/pmm-server"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "goreleaser/goreleaser" = "docker.io/goreleaser/goreleaser"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "moby/buildkit" = "docker.io/moby/buildkit"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "mongo" = "docker.io/library/mongo"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this way we don’t need to set &lt;code>PMM_SERVER_IMAGE&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Please also note other aliases that I have added as I progressed through this experiment, I needed them all to run later.&lt;/p>
&lt;h3 id="privileged-ports">privileged ports&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: &lt;span class="k">for&lt;/span> pmm-managed-server Cannot start service pmm-managed-server: rootlessport cannot expose privileged port 80, you can add &lt;span class="s1">'net.ipv4.ip_unprivileged_port_start=80'&lt;/span> to /etc/sysctl.conf &lt;span class="o">(&lt;/span>currently 1024&lt;span class="o">)&lt;/span>, or choose a larger port number &lt;span class="o">(&lt;/span>>&lt;span class="o">=&lt;/span> 1024&lt;span class="o">)&lt;/span>: listen tcp 127.0.0.1:80: bind: permission denied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">compose.parallel.parallel_execute_iter: Failed: &lt;Service: pmm-managed-server>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">compose.parallel.feed_queue: Pending: set&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: &lt;span class="k">for&lt;/span> pmm-managed-server Cannot start service pmm-managed-server: rootlessport cannot expose privileged port 80, you can add &lt;span class="s1">'net.ipv4.ip_unprivileged_port_start=80'&lt;/span> to /etc/sysctl.conf &lt;span class="o">(&lt;/span>currently 1024&lt;span class="o">)&lt;/span>, or choose a larger port number &lt;span class="o">(&lt;/span>>&lt;span class="o">=&lt;/span> 1024&lt;span class="o">)&lt;/span>: listen tcp 127.0.0.1:80: bind: permission denied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: compose.cli.main.exit_with_metrics: Encountered errors &lt;span class="k">while&lt;/span> bringing up the project.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: *** &lt;span class="o">[&lt;/span>Makefile:9: env-compose-up&lt;span class="o">]&lt;/span> Error &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>OK, this is common for rootless containers that system wouldn’t allow them to bind ports and could be either tuned as suggested in error message, or we just could bind to the unprivileged ports:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">127.0.0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8080&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">40443&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># For headless delve&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is fine for development purposes. In prod container should be run either under privileged user, or there should be some proxy behind it.&lt;/p>
&lt;h3 id="security-opt-parameter">security-opt parameter&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: &lt;span class="k">for&lt;/span> pmm-managed-server Cannot create container &lt;span class="k">for&lt;/span> service pmm-managed-server: fill out specgen: invalid --security-opt 1: &lt;span class="s2">"seccomp:unconfined"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://github.com/containers/podman/blob/7dabcbd7bcf78f3b5d310ed547801106da382618/pkg/specgenutil/specgen.go#L544&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>OK, that is more interesting. In &lt;code>pmm-managed&lt;/code> compose file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">security_opt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">seccomp:unconfined&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I googled it and found out &lt;a href="https://github.com/containers/podman-compose/commit/bbaa7867399b91255859b959535fedd7c20daacc" target="_blank" rel="noopener noreferrer">this fix&lt;/a> for &lt;code>podman-compose&lt;/code>. There they just replaced &lt;code>:&lt;/code> with &lt;code>=&lt;/code>.
OK, if I try that - it works:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">security_opt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">seccomp=unconfined&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It passes it correctly and podman happy.&lt;/p>
&lt;p>Probably docker would be happy as well, as they support both &lt;code>:&lt;/code> and &lt;code>=&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673" target="_blank" rel="noopener noreferrer">https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/docker/compose/blob/a9e8164a8d2796847c83a38a2f7cd9f19a13b940/pkg/compose/create.go#L401" target="_blank" rel="noopener noreferrer">https://github.com/docker/compose/blob/a9e8164a8d2796847c83a38a2f7cd9f19a13b940/pkg/compose/create.go#L401&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Looks like devs weren’t sure which one is correct or there were no standard on the date &lt;code>:&lt;/code> was added. But looks like &lt;code>=&lt;/code> is a correct one. So we need to test it with docker and just change.&lt;/p>
&lt;p>Bellow I have changed compose file with &lt;code>=&lt;/code>.&lt;/p>
&lt;h3 id="makefile-not-parametrized">Makefile not parametrized&lt;/h3>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating pmm-managed-server ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">compose.parallel.feed_queue: Pending: set&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">compose.parallel.parallel_execute_iter: Finished processing: &lt;Service: pmm-managed-server>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">compose.parallel.feed_queue: Pending: set&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it --workdir&lt;span class="o">=&lt;/span>/root/go/src/github.com/percona/pmm-managed pmm-managed-server .devcontainer/setup.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: docker: No such file or directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: *** &lt;span class="o">[&lt;/span>Makefile:12: env-devcontainer&lt;span class="o">]&lt;/span> Error &lt;span class="m">127&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now it couldn’t find &lt;code>docker&lt;/code> executable, it is hardcoded in the Makefile:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">env-devcontainer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> docker exec -it --workdir=/root/go/src/github.com/percona/pmm-managed pmm-managed-server .devcontainer/setup.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So we can’t just alias it in bash, but need a link:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo ln -s /usr/bin/podman /usr/bin/docker&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Other way to do it is to use some variable in the &lt;code>Makefile&lt;/code> to be able to take executable as a parameter.&lt;/p>
&lt;h3 id="success">Success&lt;/h3>
&lt;p>Implementing all above:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ make env-up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">> supervisorctl start pmm-managed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-managed: started
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Done in 129.057330132&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Actually not that bad, what we have done:&lt;/p>
&lt;ul>
&lt;li>prepared system environment: socket, env var, link, aliases&lt;/li>
&lt;li>fixed minor non-standard parameter&lt;/li>
&lt;/ul>
&lt;p>All of that needs to be done once and after that there is no difference on running podman, except that it runs in user mode and don’t require privileged daemon ;-)&lt;/p>
&lt;h2 id="mongodb_exporter">mongodb_exporter&lt;/h2>
&lt;p>Lets test if we can build it using &lt;code>goreleaser&lt;/code> with &lt;code>podman&lt;/code> as well as let’s try to bring up some more complex testing environment with &lt;code>docker-compose&lt;/code>.&lt;/p>
&lt;h3 id="goreleaser">goreleaser&lt;/h3>
&lt;p>&lt;a href="https://goreleaser.com/install/#running-with-docker" target="_blank" rel="noopener noreferrer">https://goreleaser.com/install/#running-with-docker&lt;/a> :&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">podman run --privileged --rm -v &lt;span class="nv">$PWD&lt;/span>:/go/src/github.com/user/repo -v /run/user/1000/podman/podman.sock:/var/run/docker.sock -w /go/src/github.com/user/repo goreleaser/goreleaser release --snapshot --skip-publish --rm-dist&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So we know that we have different socket already so we are passing it, as well as we already have alias for the short-name (for &lt;code>goreleaser&lt;/code> as well as for the &lt;code>buildx&lt;/code>). And it just works:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ podman run --privileged --rm -v &lt;span class="nv">$PWD&lt;/span>:/go/src/github.com/user/repo -v /run/user/1000/podman/podman.sock:/var/run/docker.sock -w /go/src/github.com/user/repo goreleaser/goreleaser release --snapshot --skip-publish --rm-dist
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • releasing...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building binaries
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>/go/src/github.com/user/repo/build/mongodb_exporter_darwin_arm64/mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>/go/src/github.com/user/repo/build/mongodb_exporter_darwin_amd64/mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>/go/src/github.com/user/repo/build/mongodb_exporter_linux_arm64/mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>/go/src/github.com/user/repo/build/mongodb_exporter_linux_amd64/mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building &lt;span class="nv">binary&lt;/span>&lt;span class="o">=&lt;/span>/go/src/github.com/user/repo/build/mongodb_exporter_linux_arm_7/mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • archives
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">archive&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">archive&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">archive&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">archive&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.darwin-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">archive&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.darwin-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • linux packages
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>arm7 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm.rpm &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>rpm &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>arm64 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm64.deb &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>deb &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>arm64 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm64.rpm &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>rpm &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>amd64 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-64-bit.rpm &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>rpm &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>amd64 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-64-bit.deb &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>deb &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • creating &lt;span class="nv">arch&lt;/span>&lt;span class="o">=&lt;/span>arm7 &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/mongodb_exporter-88c186c.linux-arm.deb &lt;span class="nv">format&lt;/span>&lt;span class="o">=&lt;/span>deb &lt;span class="nv">package&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • calculating checksums
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-64-bit.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-64-bit.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm64.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.darwin-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.darwin-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm64.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • checksumming &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>mongodb_exporter-88c186c.linux-arm.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • docker images
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • building docker image &lt;span class="nv">image&lt;/span>&lt;span class="o">=&lt;/span>percona/mongodb_exporter:0.30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • pipe skipped &lt;span class="nv">error&lt;/span>&lt;span class="o">=&lt;/span>publishing is disabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • storing artifact list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • writing &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>build/artifacts.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> • release succeeded after 66.48s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls -la build/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">55592&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">8&lt;/span> dkondratenko dkondratenko &lt;span class="m">4096&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxrwxr-x. &lt;span class="m">11&lt;/span> dkondratenko dkondratenko &lt;span class="m">4096&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:34 ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-------. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">9932&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 artifacts.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">3931&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:34 config.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwx------. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">146&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 goreleaserdocker741570390
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">1190&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_88c186c_checksums.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5555136&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.darwin-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5467991&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.darwin-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5362664&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-64-bit.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5345376&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-64-bit.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5351467&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">4914988&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm64.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">4902660&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm64.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">4908794&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm64.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5028350&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5015878&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5023920&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-arm.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">30&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_darwin_amd64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">30&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_darwin_arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">30&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_linux_amd64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">30&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_linux_arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">30&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter_linux_arm_7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls -la build/goreleaserdocker741570390/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">25144&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwx------. &lt;span class="m">2&lt;/span> dkondratenko dkondratenko &lt;span class="m">146&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x. &lt;span class="m">8&lt;/span> dkondratenko dkondratenko &lt;span class="m">4096&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">244&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 Dockerfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rwxr-xr-x. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">15024128&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5362664&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-64-bit.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--. &lt;span class="m">1&lt;/span> dkondratenko dkondratenko &lt;span class="m">5345376&lt;/span> Dec &lt;span class="m">22&lt;/span> 23:35 mongodb_exporter-88c186c.linux-64-bit.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ podman images
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">REPOSITORY TAG IMAGE ID CREATED SIZE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">localhost/percona/mongodb_exporter 88c186c 23d41a482eb4 &lt;span class="m">3&lt;/span> minutes ago 15.2 MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">localhost/percona/mongodb_exporter 0.30 23d41a482eb4 &lt;span class="m">3&lt;/span> minutes ago 15.2 MB&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="docker-compose">docker-compose&lt;/h3>
&lt;p>There is compose file to bring test environment for the &lt;code>mongodb_exporter&lt;/code>. Lets try to bring it up (also notice that mongo alias was added above to resolve short-name):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ docker-compose up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>links&lt;/code> don’t work. Also in compose doc they are kind of deprecated:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#links" target="_blank" rel="noopener noreferrer">https://docs.docker.com/compose/compose-file/compose-file-v3/#links&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>So I just deleted all links and it works:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ docker-compose up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-cnf-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-cnf-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-1-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-1-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-2-2 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-2-arbiter ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-2-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-2-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating standalone ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-1-2 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-cnf-2 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-1-arbiter ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-rs2-setup ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-cnf-setup ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-rs1-setup ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongos ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating mongo-shard-setup ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Attaching to mongo-2-arbiter, mongo-cnf-1, mongo-1-3, mongo-2-2, mongo-1-2, mongo-2-3, standalone, mongo-cnf-3, mongo-1-1, mongo-2-1, mongo-cnf-2, mongo-1-arbiter, mongo-rs2-setup, mongo-cnf-setup, mongo-rs1-setup, mongos, mongo-shard-setup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-1 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:02.806+0000 I NETWORK &lt;span class="o">[&lt;/span>listener&lt;span class="o">]&lt;/span> connection accepted from 10.89.0.33:58362 &lt;span class="c1">#56 (33 connections now open)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-1 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:02.807+0000 I NETWORK &lt;span class="o">[&lt;/span>conn56&lt;span class="o">]&lt;/span> received client metadata from 10.89.0.33:58362 conn56: &lt;span class="o">{&lt;/span> driver: &lt;span class="o">{&lt;/span> name: &lt;span class="s2">"NetworkInterfaceTL"&lt;/span>, version: &lt;span class="s2">"4.2.17"&lt;/span> &lt;span class="o">}&lt;/span>, os: &lt;span class="o">{&lt;/span> type: &lt;span class="s2">"Linux"&lt;/span>, name: &lt;span class="s2">"Ubuntu"&lt;/span>, architecture: &lt;span class="s2">"x86_64"&lt;/span>, version: &lt;span class="s2">"18.04"&lt;/span> &lt;span class="o">}&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> --- Sharding Status ---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> sharding version: &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="s2">"_id"&lt;/span> : 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="s2">"minCompatibleVersion"&lt;/span> : 5,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="s2">"currentVersion"&lt;/span> : 6,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="s2">"clusterId"&lt;/span> : ObjectId&lt;span class="o">(&lt;/span>&lt;span class="s2">"61c4fdcc0039e75de22fa8bd"&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> shards:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s2">"_id"&lt;/span> : &lt;span class="s2">"rs1"&lt;/span>, &lt;span class="s2">"host"&lt;/span> : &lt;span class="s2">"rs1/10.89.0.16:27017,10.89.0.18:27017,10.89.0.22:27017"&lt;/span>, &lt;span class="s2">"state"&lt;/span> : &lt;span class="m">1&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s2">"_id"&lt;/span> : &lt;span class="s2">"rs2"&lt;/span>, &lt;span class="s2">"host"&lt;/span> : &lt;span class="s2">"rs2/10.89.0.17:27017,10.89.0.19:27017,10.89.0.23:27017"&lt;/span>, &lt;span class="s2">"state"&lt;/span> : &lt;span class="m">1&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> active mongoses:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="s2">"4.2.17"&lt;/span> : &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> autosplit:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> Currently enabled: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> balancer:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> Currently enabled: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> Currently running: no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> Failed balancer rounds in last &lt;span class="m">5&lt;/span> attempts: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> Migration Results &lt;span class="k">for&lt;/span> the last &lt;span class="m">24&lt;/span> hours:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> No recent migrations
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> databases:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s2">"_id"&lt;/span> : &lt;span class="s2">"config"&lt;/span>, &lt;span class="s2">"primary"&lt;/span> : &lt;span class="s2">"config"&lt;/span>, &lt;span class="s2">"partitioned"&lt;/span> : &lt;span class="nb">true&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup &lt;span class="p">|&lt;/span> bye
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongos &lt;span class="p">|&lt;/span> 2021-12-23T22:53:02.833+0000 I NETWORK &lt;span class="o">[&lt;/span>conn14&lt;span class="o">]&lt;/span> end connection 10.89.0.35:40380 &lt;span class="o">(&lt;/span>&lt;span class="m">0&lt;/span> connections now open&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-shard-setup exited with code &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongos &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.806+0000 I CONNPOOL &lt;span class="o">[&lt;/span>TaskExecutorPool-0&lt;span class="o">]&lt;/span> Connecting to 10.89.0.24:27017
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongos &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.806+0000 I CONNPOOL &lt;span class="o">[&lt;/span>TaskExecutorPool-0&lt;span class="o">]&lt;/span> Connecting to 10.89.0.21:27017
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-2 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.807+0000 I NETWORK &lt;span class="o">[&lt;/span>listener&lt;span class="o">]&lt;/span> connection accepted from 10.89.0.33:47564 &lt;span class="c1">#31 (20 connections now open)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-2 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.808+0000 I NETWORK &lt;span class="o">[&lt;/span>conn31&lt;span class="o">]&lt;/span> received client metadata from 10.89.0.33:47564 conn31: &lt;span class="o">{&lt;/span> driver: &lt;span class="o">{&lt;/span> name: &lt;span class="s2">"NetworkInterfaceTL"&lt;/span>, version: &lt;span class="s2">"4.2.17"&lt;/span> &lt;span class="o">}&lt;/span>, os: &lt;span class="o">{&lt;/span> type: &lt;span class="s2">"Linux"&lt;/span>, name: &lt;span class="s2">"Ubuntu"&lt;/span>, architecture: &lt;span class="s2">"x86_64"&lt;/span>, version: &lt;span class="s2">"18.04"&lt;/span> &lt;span class="o">}&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-3 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.807+0000 I NETWORK &lt;span class="o">[&lt;/span>listener&lt;span class="o">]&lt;/span> connection accepted from 10.89.0.33:54550 &lt;span class="c1">#28 (17 connections now open)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongo-cnf-3 &lt;span class="p">|&lt;/span> 2021-12-23T22:53:03.809+0000 I NETWORK &lt;span class="o">[&lt;/span>conn28&lt;span class="o">]&lt;/span> received client metadata from 10.89.0.33:54550 conn28: &lt;span class="o">{&lt;/span> driver: &lt;span class="o">{&lt;/span> name: &lt;span class="s2">"NetworkInterfaceTL"&lt;/span>, version: &lt;span class="s2">"4.2.17"&lt;/span> &lt;span class="o">}&lt;/span>, os: &lt;span class="o">{&lt;/span> type: &lt;span class="s2">"Linux"&lt;/span>, name: &lt;span class="s2">"Ubuntu"&lt;/span>, architecture: &lt;span class="s2">"x86_64"&lt;/span>, version: &lt;span class="s2">"18.04"&lt;/span> &lt;span class="o">}&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">^CGracefully stopping... &lt;span class="o">(&lt;/span>press Ctrl+C again to force&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-2-2 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-1-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-cnf-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-cnf-2 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-2-arbiter ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-2-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-cnf-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping standalone ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongos ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-1-arbiter ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-1-3 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-2-1 ... &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stopping mongo-1-2 ... &lt;span class="k">done&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So this case shows that compose standard isn’t that stable. And those probably just could be easily removed and &lt;code>podman&lt;/code> could be used in this case as well.&lt;/p>
&lt;h2 id="selinux-notes">SELinux notes&lt;/h2>
&lt;p>If you have SELinux enabled, as I do:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ sestatus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELinux status: enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELinuxfs mount: /sys/fs/selinux
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELinux root directory: /etc/selinux
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Loaded policy name: targeted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Current mode: enforcing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Mode from config file: enforcing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Policy MLS status: enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Policy deny_unknown status: allowed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Memory protection checking: actual (secure)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Max kernel policy version: 33&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You would need some additional changes and system tunning. It is mostly related to the volume binds.&lt;/p>
&lt;h3 id="pmm-managed-1">&lt;code>pmm-managed&lt;/code>&lt;/h3>
&lt;p>Compose file for &lt;code>pmm-managed&lt;/code> has 2 volumes binded that without additional option would through errors like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it --workdir&lt;span class="o">=&lt;/span>/root/go/src/github.com/percona/pmm-managed pmm-managed-server .devcontainer/setup.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/usr/bin/python2: can&lt;span class="s1">'t open file '&lt;/span>/root/go/src/github.com/percona/pmm-managed/.devcontainer/setup.py&lt;span class="err">'&lt;/span>: &lt;span class="o">[&lt;/span>Errno 13&lt;span class="o">]&lt;/span> Permission denied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: *** &lt;span class="o">[&lt;/span>Makefile:12: env-devcontainer&lt;span class="o">]&lt;/span> Error &lt;span class="m">2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>and in case of &lt;code>go-modules&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">go: downloading golang.org/x/perf v0.0.0-20210220033136-40a54f11e909
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir /root/go/pkg/mod/cache: permission denied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tools.go:37: running &lt;span class="s2">"go"&lt;/span>: &lt;span class="nb">exit&lt;/span> status &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: *** &lt;span class="o">[&lt;/span>init&lt;span class="o">]&lt;/span> Error &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Traceback &lt;span class="o">(&lt;/span>most recent call last&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/root/go/src/github.com/percona/pmm-managed/.devcontainer/setup.py"&lt;/span>, line 129, in &lt;module>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> main&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/root/go/src/github.com/percona/pmm-managed/.devcontainer/setup.py"&lt;/span>, line 116, in main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> make_init&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/root/go/src/github.com/percona/pmm-managed/.devcontainer/setup.py"&lt;/span>, line 75, in make_init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">"make init"&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/root/go/src/github.com/percona/pmm-managed/.devcontainer/setup.py"&lt;/span>, line 19, in run_commands
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> subprocess.check_call&lt;span class="o">(&lt;/span>cmd, &lt;span class="nv">shell&lt;/span>&lt;span class="o">=&lt;/span>True&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &lt;span class="s2">"/usr/lib64/python2.7/subprocess.py"&lt;/span>, line 542, in check_call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> raise CalledProcessError&lt;span class="o">(&lt;/span>retcode, cmd&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">subprocess.CalledProcessError: Command &lt;span class="s1">'make init'&lt;/span> returned non-zero &lt;span class="nb">exit&lt;/span> status &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make: *** &lt;span class="o">[&lt;/span>Makefile:12: env-devcontainer&lt;span class="o">]&lt;/span> Error &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Documentation for &lt;code>podman-run&lt;/code> &lt;a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options" target="_blank" rel="noopener noreferrer">clarifies it&lt;/a>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">To change a label in the container context, you can add either of two suffixes :z or :Z to the volume mount. These suffixes tell Podman to relabel file objects on the shared volumes. The z option tells Podman that two containers share the volume content. As a result, Podman labels the content with a shared content label. Shared volume labels allow all containers to read/write content. The Z option tells Podman to label the content with a private unshared label.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here we need &lt;code>:Z&lt;/code> option:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.:/root/go/src/github.com/percona/pmm-managed:Z&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./Makefile.devcontainer:/root/go/src/github.com/percona/pmm-managed/Makefile:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">go-modules:/root/go/pkg/mod:Z&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Put modules cache into a separate volume&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="mongodb-exporter">&lt;code>mongodb-exporter&lt;/code>&lt;/h3>
&lt;p>Compose for &lt;code>mongodb_exporter&lt;/code> also contains volume binds, but it is shared across different container abd thus needs to be binded with &lt;code>:z&lt;/code> option:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./docker/scripts:/scripts:z&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here is additional info:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#volumes-from-container-options" target="_blank" rel="noopener noreferrer">https://docs.podman.io/en/latest/markdown/podman-run.1.html#volumes-from-container-options&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/containers/podman/issues/10779" target="_blank" rel="noopener noreferrer">https://github.com/containers/podman/issues/10779&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options" target="_blank" rel="noopener noreferrer">https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="mongodb-selinux">MongoDB SELinux&lt;/h3>
&lt;p>&lt;a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/#std-label-install-rhel-configure-selinux" target="_blank" rel="noopener noreferrer">https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/#std-label-install-rhel-configure-selinux&lt;/a>&lt;/p>
&lt;h2 id="devcontainers">devcontainers&lt;/h2>
&lt;p>If someone uses VSCode and would like to use devcontainer which &lt;code>pmm-managed&lt;/code> supports, podman is also &lt;a href="https://code.visualstudio.com/docs/remote/containers#_can-i-use-podman-instead-of-docker" target="_blank" rel="noopener noreferrer">supported&lt;/a>.&lt;/p>
&lt;p>I do have VSCode, but I use it in flatpak. Setting up that is a little bit tricky, and I didn’t manage it as I don’t really care and don’t want to spend time to figure it out.&lt;/p>
&lt;p>But here are couple of useful links:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/flathub/com.visualstudio.code/issues/55" target="_blank" rel="noopener noreferrer">https://github.com/flathub/com.visualstudio.code/issues/55&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://gist.github.com/FilBot3/4424d312a87f7b4178722d3b5eb20212" target="_blank" rel="noopener noreferrer">https://gist.github.com/FilBot3/4424d312a87f7b4178722d3b5eb20212&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>I don’t have docker installed for a long time and don’t struggle without it much. As shown above it is easy to setup the system and with minor changes and without obsolete parameters it would work for both docker and podman.&lt;/p>
&lt;p>The way to go from compose files is probably k8s manifests that podman supports with &lt;code>podman generate kube&lt;/code> and &lt;code>podman play kube&lt;/code>. Those are more standard and widely used.&lt;/p></content:encoded><author>Denys Kondratenko</author><category>PMM</category><category>docker-compose</category><category>goreleaser</category><category>docker</category><category>podman</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>Percona Supports Community Events - Organize Yours With Us!</title><link>https://percona.community/blog/2021/12/23/community-events-support/</link><guid>https://percona.community/blog/2021/12/23/community-events-support/</guid><pubDate>Thu, 23 Dec 2021 00:00:00 UTC</pubDate><description>One of the things we love doing in Percona is participating in community events and inspiring people to stay connected in the changing world. We have experience in organizing community dinners, meetups, lectures at universities, booths, and even conferences both in-person and virtually. Percona speakers give talks on events all around the globe.</description><content:encoded>&lt;p>One of the things we love doing in Percona is participating in &lt;a href="https://percona.community/events/" target="_blank" rel="noopener noreferrer">community events&lt;/a> and inspiring people to stay connected in the changing world. We have experience in organizing community dinners, &lt;a href="https://percona.community/events/balalaika-meetup-ulyanovsk-2021/" target="_blank" rel="noopener noreferrer">meetups&lt;/a>, &lt;a href="https://percona.community/events/national_university_chernihiv_polytechnic/" target="_blank" rel="noopener noreferrer">lectures&lt;/a> at universities, &lt;a href="https://percona.community/events/ato-2021/" target="_blank" rel="noopener noreferrer">booths&lt;/a>, and even &lt;a href="https://www.percona.com/conferences/percona-live-online-2021" target="_blank" rel="noopener noreferrer">conferences&lt;/a> both in-person and virtually. Percona speakers give talks on events all around the globe.&lt;/p>
&lt;p>Have you ever thought about organizing a themed meeting for your local open source community? It may harbor many possibilities for you. New acquaintances might open career opportunities or bring a collaboration on a new project. If you solve a difficult task on your work, discussing it with people from the community can inspire you to look at it from a different angle and benefit from the experience of other people. And last but not least is the pleasure of meeting new like-minded people either online or in-person! If in your location gatherings of open source lovers are rare, maybe you are the one to organize the next?&lt;/p>
&lt;p>If you are already stoked about this idea, here are some simple steps to take. And Percona is ready to reinforce you on your way!&lt;/p>
&lt;ol>
&lt;li>Define the format of the event, find a location, and set the date. If it is planned as an in-person one, make sure to take into consideration health policies and restrictions in your area.&lt;/li>
&lt;li>Find volunteers to help you. Contact the Marketing or HR department in your company and ask for support on this journey. Share this experience with your friends and colleagues!&lt;/li>
&lt;li>Create a form for attendees to fill in, so you could be aware of how many people are interested to go. Some resources, for example, &lt;a href="https://www.eventbrite.com/" target="_blank" rel="noopener noreferrer">Eventbrite&lt;/a> are free to use for free events.&lt;/li>
&lt;li>Contact various IT companies, universities, or educational organizations to find sponsors or speakers. Sponsoring may include not only financial assistance but also marketing and social media coverage, access to the venue at no cost, participation of invited experts, swag and gifts for attendees, etc.&lt;/li>
&lt;li>Advertise your event on social media: do regular tweets, post on LinkedIn and Facebook. Do not underestimate your network - notify all of your friends from the open source world and ask them to share the information!&lt;/li>
&lt;li>Think of the supplies you might need and order or rent them. It might be pull-up banners to advertise your sponsors, swag, paper with pens for your attendees, a flipchart with markers, a monitor or screen for your speakers to demonstrate slides, etc. If you don’t have many attendees, it might be a better solution to ask your sponsors to provide swag rather than order it. Also, don’t forget to make sure that you will have access to electrical outlets at the venue if necessary.&lt;/li>
&lt;li>And here it is, the day of the event! Organize some social activities for attendees to motivate them to spread the word about your event and your sponsors. For example, give away swag for sharing a picture on social media or arrange a prize drawing.&lt;/li>
&lt;li>Don’t forget to take some pictures and, of course, have fun! Share the pictures with attendees post-event and publish them on socials.&lt;/li>
&lt;/ol>
&lt;p>Planning events is thrilling, rewarding, and not that difficult as it may look at first sight. And &lt;strong>Percona is ready to support you!&lt;/strong> Just contact us at &lt;strong>&lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a>&lt;/strong> to tell us about your meetup or lecture and discuss sponsorship opportunities. We can share our expertise on event organizing, guide you, find a Percona speaker with online or even in-person presentation, sponsor your event, advertise it on our social or provide swag for it. The preference is given to small local community events to avoid long-distance travel for attendees and stay safe and eco-friendly. We are open to new opportunities and would love to work with you. We look forward to meeting you next year!&lt;/p></content:encoded><author>Aleksandra Abramova</author><category>PMM</category><category>PostgreSQL</category><category>PG</category><category>MySQL</category><category>MongoDB</category><category>events</category><media:thumbnail url="https://percona.community/blog/2021/12/community_hu_b6facface25cc1e4.jpg"/><media:content url="https://percona.community/blog/2021/12/community_hu_f6c97e8d5df627a2.jpg" medium="image"/></item><item><title>PMM development and testing with help of minikube</title><link>https://percona.community/blog/2021/12/20/pmm-minikube-postgres/</link><guid>https://percona.community/blog/2021/12/20/pmm-minikube-postgres/</guid><pubDate>Mon, 20 Dec 2021 00:00:00 UTC</pubDate><description>Why Some time ago I needed to test PG14 with the new pg_stat_monitor version that wasn’t released. I decided to log my journey so I would spend less effort next time to replicate it.</description><content:encoded>&lt;h2 id="why">Why&lt;/h2>
&lt;p>Some time ago I needed to test PG14 with the new &lt;code>pg_stat_monitor&lt;/code> version that wasn’t released. I decided to log my journey so I would spend less effort next time to replicate it.&lt;/p>
&lt;p>I do use podman and run PMM with its help but I also like to hack PMM DBaaS features and think that k8s and minikube are perfect and better scalable solutions for the different development environments and especially for the number of clusters and DBs.
If I need just PMM I would run it with podman, but while I am already hacking around DBaaS, I would like to use the same tool for my other development activities.&lt;/p>
&lt;p>So my goal is to deploy PMM on minikube, deploy PG14 there with new &lt;code>pg_stat_monitor&lt;/code> and check that PMM has support of new fields and features in &lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/using/query-analytics.html" target="_blank" rel="noopener noreferrer">QAN&lt;/a>.&lt;/p>
&lt;h2 id="minikube">&lt;code>minikube&lt;/code>&lt;/h2>
&lt;p>I use Linux and in the examples bellow I run Fedora 35.&lt;/p>
&lt;p>First of all you would need minikube - &lt;a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank" rel="noopener noreferrer">install it&lt;/a>.&lt;/p>
&lt;p>I had a clean system and &lt;code>podman&lt;/code> and &lt;code>buildah&lt;/code> installed. When you first start a minikube with &lt;code>minikube start&lt;/code> it searches for available drivers and tries to deploy kubernetes on top of it. In my case it found podman and provided me with instruction that I needed to follow to get minikube correctly use podman driver.&lt;/p>
&lt;p>After setting everything up I was ready to go:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">😄 minikube v1.24.0 on Fedora &lt;span class="m">35&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✨ Using the podman driver based on existing profile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">👍 Starting control plane node minikube in cluster minikube
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚜 Pulling base image ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🔄 Restarting existing podman container &lt;span class="k">for&lt;/span> &lt;span class="s2">"minikube"&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🐳 Preparing Kubernetes v1.22.3 on Docker 20.10.8 ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🔎 Verifying Kubernetes components...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🌟 Enabled addons: storage-provisioner, default-storageclass
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🏄 Done! kubectl is now configured to use &lt;span class="s2">"minikube"&lt;/span> cluster and &lt;span class="s2">"default"&lt;/span> namespace by default&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>OK, that was easy.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- get nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> > kubectl.sha256: &lt;span class="m">64&lt;/span> B / &lt;span class="m">64&lt;/span> B &lt;span class="o">[&lt;/span>--------------------------&lt;span class="o">]&lt;/span> 100.00% ? p/s 0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> > kubectl: 44.73 MiB / 44.73 MiB &lt;span class="o">[&lt;/span>-------------&lt;span class="o">]&lt;/span> 100.00% 36.08 MiB p/s 1.4s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">minikube Ready control-plane,master 28d v1.22.3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Minikube has it’s own kubectl in case you don’t have one installed. If you do it would configure it to use the correct kubernetes config to access k8s it has deployed.&lt;/p>
&lt;p>I had some &lt;a href="https://github.com/kubernetes/minikube/issues/12569#issuecomment-932732865" target="_blank" rel="noopener noreferrer">issue&lt;/a> while deploying on my btrfs root file system, and I could workaround it starting it with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube start --feature-gates&lt;span class="o">=&lt;/span>&lt;span class="s2">"LocalStorageCapacityIsolation=false"&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="pmm-in-k8s">PMM in k8s&lt;/h2>
&lt;p>PMM currently doesn’t have native k8s support as the container has root privileges and is tightly integrated with different components.&lt;/p>
&lt;p>But it is fine for running in staging and testing environments.&lt;/p>
&lt;p>To deploy PMM there are 2 ways:&lt;/p>
&lt;ol>
&lt;li>hard one with persistent storage&lt;/li>
&lt;li>easy one with ephemeral storage&lt;/li>
&lt;/ol>
&lt;p>The hard one is longer and could break anytime. The option #1 is used in &lt;a href="https://www.percona.com/blog/2021/05/19/percona-monitoring-and-management-dbaas-overview-and-technical-details/" target="_blank" rel="noopener noreferrer">this blog&lt;/a> post and you could use &lt;a href="https://github.com/percona-platform/dbaas-controller/blob/main/deploy/pmm-server-minikube.yaml" target="_blank" rel="noopener noreferrer">this yaml&lt;/a> file to see how to do it.&lt;/p>
&lt;p>I need to quickly run testa and don’t care if data disappears (ephemeral storage) neither for PMM nor for DB. So I wrote this quick deployment &lt;code>pmm-k8s-ephemeral.yaml&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NodePort&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodePort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30080&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">api&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodePort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-net&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app.kubernetes.io/part-of&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-server&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vm-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8428&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ConfigMap&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-conf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app.kubernetes.io/part-of&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_ADDRESS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-net:443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'true'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_DEBUG&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'true'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_TRACE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'true'&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_CONFIG_FILE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"/usr/local/percona/pmm2/config/pmm-agent.yaml"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SETUP_METRICS_MODE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"push"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PMM_AGENT_SERVER_INSECURE_TLS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"true"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app.kubernetes.io/part-of&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Recreate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app.kubernetes.io/part-of&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-server&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker.io/perconalab/pmm-server:2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">api&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8428&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vm&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What I have done there:&lt;/p>
&lt;ul>
&lt;li>Service (pmm) that will expose PMM to the local machine so I can reach PMM in the browser&lt;/li>
&lt;li>Service (pmm-net) for tools and PMM client to contact PMM server and send monitoring and analytics&lt;/li>
&lt;li>ConfigMap (pmm-conf) with parameters for the PMM client&lt;/li>
&lt;li>Deployment that runs PMM container and exposes couple of ports for the Services&lt;/li>
&lt;/ul>
&lt;p>Let’s deploy it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- apply -f pmm-k8s-ephemeral.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service/pmm-net created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">configmap/pmm-conf created
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deployment.apps/pmm-deployment created&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Nice, is it running?&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-deployment-d785ff89f-rz8zp 1/1 Running &lt;span class="m">0&lt;/span> 61s&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lets open PMM in the browser:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">ssh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-deployment-d785ff89f-rz8zp 1/1 Running 0 61s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube service pmm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|-----------|------|-------------|---------------------------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| NAMESPACE | NAME | TARGET PORT | URL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|-----------|------|-------------|---------------------------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| default | pmm | web/80 | http://192.168.49.2:30080 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| | | api/443 | http://192.168.49.2:30443 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|-----------|------|-------------|---------------------------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🎉 Opening service default/pmm in default browser...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Opening in existing browser session.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And it will open a couple of links, if you would like to use one on &lt;code>30443&lt;/code> port - add &lt;code>https://&lt;/code> before the IP. The user/pass is &lt;code>admin/admin&lt;/code>.&lt;/p>
&lt;p>OK, now I see the PMM and it is working.&lt;/p>
&lt;p>Lets connect some clients to it.&lt;/p>
&lt;h2 id="pg14-with-pg_stat_monitor">PG14 with pg_stat_monitor&lt;/h2>
&lt;p>For my task I need to take vanila PG14 and add &lt;code>pg_stat_monitor&lt;/code> to it, as it doesn’t come as a part of standard container distribution. Percona has &lt;a href="https://www.percona.com/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> which comes with &lt;code>pg_stat_monitor&lt;/code> installed, but it wouldn’t work for me as I need unreleased version and it also wasn’t available with PG14.&lt;/p>
&lt;p>First I need to build &lt;a href="https://github.com/percona/pg_stat_monitor" target="_blank" rel="noopener noreferrer">pg_stat_monitor&lt;/a>. There are &lt;a href="https://github.com/percona/pg_stat_monitor#building-from-source" target="_blank" rel="noopener noreferrer">instructions&lt;/a> so lets do it but I would use &lt;code>toolbox&lt;/code> to not pollute my host system:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git clone https://github.com/percona/pg_stat_monitor.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> pg_stat_monitor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ toolbox create pg_mon
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating container pg_mon: &lt;span class="p">|&lt;/span> Created container: pg_mon
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter with: toolbox enter pg_mon
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ toolbox enter pg_mon
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ sudo dnf module reset postgresql -y
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ sudo dnf module &lt;span class="nb">enable&lt;/span> postgresql:14
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ sudo dnf install make gcc redhat-rpm-config postgresql-server-devel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ make &lt;span class="nv">USE_PGXS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ ls -la ?&lt;span class="o">(&lt;/span>*.sql&lt;span class="p">|&lt;/span>*.so&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-rw-r--. &lt;span class="m">1&lt;/span> user user &lt;span class="m">6904&lt;/span> Dec &lt;span class="m">17&lt;/span> 22:23 pg_stat_monitor--1.0.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rwxr-xr-x. &lt;span class="m">1&lt;/span> user user &lt;span class="m">253328&lt;/span> Dec &lt;span class="m">17&lt;/span> 22:23 pg_stat_monitor.so
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">⬢&lt;span class="o">[&lt;/span>pg_stat_monitor&lt;span class="o">]&lt;/span>$ exit&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>OK, so I have a new &lt;code>pg_stat_monitor&lt;/code> that I built from the &lt;code>main&lt;/code> branch.&lt;/p>
&lt;p>Now I need to embed it into the standard PG14 container:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nv">container&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>buildah from postgres&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah copy &lt;span class="nv">$container&lt;/span> ./pg_stat_monitor.so /usr/lib/postgresql/14/lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cea14ac2e80f79232619557c6e2a7fb2f2379dc5216a67b775905819f5f5c730
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah copy &lt;span class="nv">$container&lt;/span> ./pg_stat_monitor.bc /usr/lib/postgresql/14/lib/bitcode/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4d24aedb673a86a09883336657f6abaf20327ff21ec7a1885e2018a32a548f57
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah copy &lt;span class="nv">$container&lt;/span> ./pg_stat_monitor.bc /usr/lib/postgresql/14/lib/bitcode/pg_stat_monitor/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4d24aedb673a86a09883336657f6abaf20327ff21ec7a1885e2018a32a548f57
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah copy &lt;span class="nv">$container&lt;/span> ./pg_stat_monitor--1.0.sql usr/share/postgresql/14/extension/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ff06a0a8c94bcfe92b8b3616c5791a8e54180a1d9730c6c26c42400741a793dd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah copy &lt;span class="nv">$container&lt;/span> ./pg_stat_monitor.control usr/share/postgresql/14/extension/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ec90a547ee46e628ad853c7e4a0afc6aa6ba39677e9adcf04c22bd820dc9aa4b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah run &lt;span class="nv">$container&lt;/span> -- sh -c &lt;span class="s2">"echo shared_preload_libraries = \'pg_stat_monitor\' >> /usr/share/postgresql/postgresql.conf.sample"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ buildah commit &lt;span class="nv">$container&lt;/span> postgresql-pg-stat-monitor-test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Getting image &lt;span class="nb">source&lt;/span> signatures
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 9321ff862abb skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1fd9b284a3ce skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob e408a39a0b68 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 8083ac6c7a07 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 16bdcb6f65a3 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 470529a805d0 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 51e951dc5705 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 27051a077cdc skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob dd44883ded8b skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1b8d5d101e2a skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 806c98b52cc8 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1fb1b8252a25 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 20371ceade59 skipped: already exists
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 94a669b6abd4 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying config 381f3d202a &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing manifest to image destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Storing signatures
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">381f3d202aca494c2caa663dfa1f95934c3a0bb64e0efceb0388ff6f3854be08
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ podman save --format docker-archive -o postgresql-pg-stat-monitor-test.tar localhost/postgresql-pg-stat-monitor-test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 9321ff862abb &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1fd9b284a3ce &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob e408a39a0b68 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 8083ac6c7a07 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 16bdcb6f65a3 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 470529a805d0 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 51e951dc5705 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 27051a077cdc &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob dd44883ded8b &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1b8d5d101e2a &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 806c98b52cc8 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 1fb1b8252a25 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 20371ceade59 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying blob 94a669b6abd4 &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copying config 381f3d202a &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing manifest to image destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Storing signatures
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube image load ./postgresql-pg-stat-monitor-test.tar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube image ls
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker.io/localhost/postgresql-pg-stat-monitor-test:latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What I have done there:&lt;/p>
&lt;ul>
&lt;li>created new image from &lt;code>docker.io/library/postgres&lt;/code>&lt;/li>
&lt;li>copied all needed files from locally built &lt;code>pg_stat_monitor&lt;/code> to the new image&lt;/li>
&lt;li>enabled &lt;code>pg_stat_monitor&lt;/code> in the config&lt;/li>
&lt;li>commited changes to the image&lt;/li>
&lt;li>saved image to the archive&lt;/li>
&lt;li>loaded image from the archive to the minikube cache (if anyone know how to load local image directly - please let me know)&lt;/li>
&lt;/ul>
&lt;p>So now I have a PG14 image with the new &lt;code>pg_stat+monitor&lt;/code> that I would like to test as the image in my k8s cluster.&lt;/p>
&lt;p>Lets create a PG14 deployment, shall we? Here is &lt;code>postgresql_eph.yaml&lt;/code> file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ConfigMap&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres-configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">StatefulSet&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres-statefulset&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"postgres"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker.io/localhost/postgresql-pg-stat-monitor-test:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">imagePullPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Never&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">envFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">configMapRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres-configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker.io/perconalab/pmm-client:2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">envFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">configMapRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pmm-conf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8428&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vm&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Notice here &lt;code>imagePullPolicy: Never&lt;/code> and &lt;code>image: docker.io/localhost/postgresql-pg-stat-monitor-test:latest&lt;/code>, I am instructing k8s to not pull the image but only use one in cache and it is the image name that I have uploaded earlier.&lt;/p>
&lt;p>I also added a PMM client sidecar container to monitor and query PG14. Also notice that PG14 is not exposed outside of the pod, I just don’t need it. The load I need I could produce just from inside of the pod. So if you use this example for something else - expose the port for PG14.&lt;/p>
&lt;p>Lets deploy it:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- apply -f ./postgresql_eph.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- get pods
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm-deployment-d785ff89f-sgr6s 1/1 Running &lt;span class="m">0&lt;/span> 11m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">postgres-statefulset-0 2/2 Running &lt;span class="m">0&lt;/span> 11m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now I have PMM and PG14 running, let’s connect them.&lt;/p>
&lt;h2 id="pmm-qan-with-pg_stat_monitor">PMM QAN with &lt;code>pg_stat_monitor&lt;/code>&lt;/h2>
&lt;p>First of all I need to enable &lt;code>pg_stat_monitor&lt;/code> extension for PG14:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- &lt;span class="nb">exec&lt;/span> --stdin --tty postgres-statefulset-0 --container postgres -- /bin/bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root@postgres-statefulset-0:/# psql -U admin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql &lt;span class="o">(&lt;/span>14.1 &lt;span class="o">(&lt;/span>Debian 14.1-1.pgdg110+1&lt;span class="o">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Type &lt;span class="s2">"help"&lt;/span> &lt;span class="k">for&lt;/span> help.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">admin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="c1"># CREATE EXTENSION pg_stat_monitor;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CREATE EXTENSION
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">admin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="c1"># SELECT pg_stat_monitor_version();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pg_stat_monitor_version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-------------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.0.0-rc.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>&lt;span class="m">1&lt;/span> row&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">admin-# &lt;span class="se">\q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root@postgres-statefulset-0:/# exit&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now I need connect PG14 to the PMM client so it would start monitor it and scrape query analytics:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sh&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ minikube kubectl -- &lt;span class="nb">exec&lt;/span> --stdin --tty postgres-statefulset-0 --container pmm-agent -- /bin/bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bash-4.2$ pmm-admin list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Service &lt;span class="nb">type&lt;/span> Service name Address and port Service ID
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Agent &lt;span class="nb">type&lt;/span> Status Metrics Mode Agent ID Service ID
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pmm_agent Connected /agent_id/318838db-bd57-44d4-b7a7-3786ec2492f0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">node_exporter Running push /agent_id/58ef7f93-cf83-4d5b-bd2b-be34b7fc5ecf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vmagent Running push /agent_id/d29685ba-61e0-429f-84f5-4f85505242dc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bash-4.2$ pmm-admin add postgresql --username&lt;span class="o">=&lt;/span>admin --password&lt;span class="o">=&lt;/span>admin --query-source&lt;span class="o">=&lt;/span>pgstatmonitor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PostgreSQL Service added.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Service ID : /service_id/736b6453-23d2-45f1-b30e-2bacccca3644
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Service name: postgres-statefulset-0-postgresql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now if I go to the PMM UI, I could see the QAN data for the postgres, or debug why I don’t see it :)&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>minikube is a very nice tool for developers and testers to bring up complex deployments, play, debug and test.&lt;/p>
&lt;p>k8s yaml &lt;a href="https://kubernetes.io/docs/reference/glossary/?fundamental=true#term-manifest" target="_blank" rel="noopener noreferrer">manifests&lt;/a> are really good standardized and have tons of configurable options as ConfigMaps, Secrets and etc. Which you could have different in testing, staging and production but sharing same operators, deployments and pods. It also has a clear, documented, open source API and code.&lt;/p>
&lt;p>Also podman has &lt;code>play kube&lt;/code> feature that allows the reuse of the same manifest files to create pods with podman. It is not fully featured, but potential and ideas are very powerful.&lt;/p>
&lt;p>Check out &lt;a href="https://kubernetespodcast.com/episode/164-podman/" target="_blank" rel="noopener noreferrer">Kubernetes Podcast&lt;/a> to learn more about podman.&lt;/p></content:encoded><author>Denys Kondratenko</author><category>PMM</category><category>PostgreSQL</category><category>PG</category><category>pg_stat_monitor</category><category>minikube</category><category>podman</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>2.25.0 Preview Release</title><link>https://percona.community/blog/2021/12/03/preview-release/</link><guid>https://percona.community/blog/2021/12/03/preview-release/</guid><pubDate>Fri, 03 Dec 2021 00:00:00 UTC</pubDate><description>2.25.0 Preview Release Percona Monitoring and Management 2.25.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="2250-preview-release">2.25.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.25.0 is now available as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments&lt;/strong> only, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release Notes can be found &lt;a href="https://docs.percona.com/percona-monitoring-and-management/release-notes/2.25.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag: perconalab/pmm-server:2.25.0-rc&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.25.0 from this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3300.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable original testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3-website-us-east-1.amazonaws.com/PMM2-Server-2021-12-03-1855.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2021-12-03-1855.ova&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html&lt;/a>&lt;/p>
&lt;p>Artifact id: &lt;code>ami-04ba67eb15e6e089e&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/blog/2021/12/preview_225_hu_b57661f11988d885.jpg"/><media:content url="https://percona.community/blog/2021/12/preview_225_hu_7501f4a76d1ef9da.jpg" medium="image"/></item><item><title>2.24.0 Preview Release</title><link>https://percona.community/blog/2021/11/11/preview-release/</link><guid>https://percona.community/blog/2021/11/11/preview-release/</guid><pubDate>Thu, 11 Nov 2021 00:00:00 UTC</pubDate><description>2.24.0 Preview Release Percona Monitoring and Management 2.24.0 is now available as a Preview Release.</description><content:encoded>&lt;h2 id="2240-preview-release">2.24.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.24.0 is now available as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments&lt;/strong> only, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Release Notes can be found &lt;a href="https://deploy-preview-622--pmm-doc.netlify.app/release-notes/2.24.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag: &lt;a href="https://hub.docker.com/layers/perconalab/pmm-server/2.24.0-rc/images/sha256-e59fbdf2ffe7e30a3eb3cc83c438130bcecd8ca6ea02ef04c8a121fbb81a948a?context=explore" target="_blank" rel="noopener noreferrer">perconalab/pmm-server:2.24.0-rc&lt;/a>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.24.0 from this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3216.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable original testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3-website-us-east-1.amazonaws.com/PMM2-Server-2021-11-10-1310.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2021-11-10-1310.ova&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html&lt;/a>&lt;/p>
&lt;p>Artifact id: &lt;code>ami-03db8ae0f3ef49618&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/blog/2021/11/preview_224_hu_7fcb27e8f8ae902d.jpg"/><media:content url="https://percona.community/blog/2021/11/preview_224_hu_32b78b028248d4eb.jpg" medium="image"/></item><item><title>The Errant GTID</title><link>https://percona.community/blog/2021/11/08/the-errant-gtid-pt1/</link><guid>https://percona.community/blog/2021/11/08/the-errant-gtid-pt1/</guid><pubDate>Mon, 08 Nov 2021 00:00:00 UTC</pubDate><description>Part 1 What is a GTID? Oracle/MySQL define a GTID as "A global transaction identifier (GTID) is a unique identifier created and associated with each transaction committed on the server of origin (the source). This identifier is unique not only to the server on which it originated, but is unique across all servers in a given replication topology." An errant transaction can make promotion of a replica to primary very difficult.</description><content:encoded>&lt;h1 id="part-1">Part 1&lt;/h1>
&lt;p>What is a GTID? Oracle/MySQL define a GTID as "A global transaction identifier (GTID) is a unique identifier created and associated with each transaction committed on the server of origin (the source). This identifier is unique not only to the server on which it originated, but is unique across all servers in a given replication topology."
&lt;p>An errant transaction can make
promotion of a replica to primary very difficult.&lt;/p>
&lt;p>An errant transaction is BAD. Why is it bad? The errant transaction could still be in the replicas binlog so when it becomes the new primary these event will get sent to other replicas causing data corruption or breaking replication.&lt;/p>
&lt;h4 id="its-easy-to-prevent-errant-transaction">Its easy to prevent errant transaction.&lt;/h4>
&lt;ol>
&lt;li>&lt;code>read_only = ON&lt;/code> in the replicas my.cnf&lt;/li>
&lt;li>Disable binlogs when you need to perform work on a replica. &lt;code>set session sql_log_bin = 'off';&lt;/code> before your work on replica. &lt;code>set session sql_log_bin = 'on';&lt;/code> when your work is complete.&lt;/p>&lt;/li>
&lt;/ol>
&lt;h4 id="find-and-correct-errant-transaction">Find and correct errant transaction&lt;/h4>
&lt;p>How do you correct an errant transaction? Compare the &lt;code>gtid_executed&lt;/code> on the primary and replica. Identify the errant transaction on the replica and then apply that transaction to the primary.&lt;/p>
&lt;p>I will show you one method in the steps below.&lt;/p>
&lt;ol>
&lt;li>On the replica run &lt;code>show variables like 'gtid_executed'&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>You will receive output similar to this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> show variables like 'gtid_executed'\G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*************************** 1. row ***************************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable_name: gtid_executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Value: 858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Make note of the gtid_executed value. You will need this to check if you have an errant transaction.&lt;/p>
&lt;ol start="2">
&lt;li>On the primary run &lt;code>show variables like 'gtid_executed'&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>You will receive output similar to this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_primary> show variables like 'gtid_executed'\G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*************************** 1. row ***************************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable_name: gtid_executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Value: 858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Make note of the gtid_executed value. You will need this to check if you have an errant transaction.&lt;/p>
&lt;ol start="3">
&lt;li>We need to determine if the replica has any errant transaction’s. We will use the function: ‘gtid_subset’ to compare the executed GTID set from &lt;strong>replica&lt;/strong> and &lt;strong>primary&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> select gtid_subset('858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2','858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2') as subset;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| subset |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Subset = 1 tells us we have &lt;strong>no&lt;/strong> errant transactions.&lt;/p>
&lt;p>Now we need to introduce an errant transaction in to the replica. Let’s do something simple by creating a new database.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> create database community;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 1 row affected (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lets repeat step 1 from above.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> show variables like 'gtid_executed'\G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*************************** 1. row ***************************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable_name: gtid_executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Value: 858d4d54-3fe1-11ec-a7e8-080027ae8b99:1-2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We will repeat step 3 using the &lt;strong>new gtid_executed&lt;/strong> from the replica and the &lt;strong>original gtid_executed&lt;/strong> from the primary.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> select gtid_subset('858d4d54-3fe1-11ec-a7e8-080027ae8b99:1-2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2','858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2') as subset;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| subset |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 0 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Subset = 0 tells us that this replica has errant transactions.&lt;/p>
&lt;p>Now we need to determine the errant transaction. We will need to subtract the &lt;code>replica executed GTID&lt;/code> from the &lt;code>primary executed GTID&lt;/code>. To do this we will use the &lt;code>gtid_subtract&lt;/code> function.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">`mysql_replica> select gtid_subtract('858d4d54-3fe1-11ec-a7e8-080027ae8b99:1-2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2','858d4d54-3fe1-11ec-a7e8-080027ae8b99:1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '> a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2') as errant;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| errant |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 858d4d54-3fe1-11ec-a7e8-080027ae8b99:2 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we have our errant transaction from the replica &lt;code>858d4d54-3fe1-11ec-a7e8-080027ae8b99:2&lt;/code>&lt;/p>
&lt;h4 id="repair-the-issue">Repair the issue&lt;/h4>
&lt;p>Now let’s move to the &lt;strong>primary&lt;/strong>.&lt;/p>
&lt;p>Once on the &lt;strong>primary&lt;/strong> we want to insert a pseudo transaction to resolve the errant transaction from the replica.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_primary> set gtid_next='858d4d54-3fe1-11ec-a7e8-080027ae8b99:2';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql_primary> begin;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`mysql_primary> commit;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`mysql_primary> set gtid_next='automatic';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can compare the GTID executed again from the replica and primary.&lt;/p>
&lt;h4 id="primary">Primary:&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_primary> show variables like 'gtid_executed'\G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*************************** 1. row ***************************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable_name: gtid_executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Value: 858d4d54-3fe1-11ec-a7e8-080027ae8b99:1-2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="replica">Replica:&lt;/h4>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql_replica> show variables like 'gtid_executed'\G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*************************** 1. row ***************************
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Variable_name: gtid_executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Value: 858d4d54-3fe1-11ec-a7e8-080027ae8b99:1-2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a6b3751e-3fd3-11ec-a4f5-080027ae8b99:1-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)`&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note that both values match. We have repaired the errant transaction from the replica to the primary.&lt;/p>
&lt;p>Now we need to take care of the replica that had the errant transaction. We need to flush and purge the binary logs. Use the commands below to find the current binary file, and then flush and purge. &lt;strong>Remember to be on the replica&lt;/strong>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">show binary logs;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FLUSH LOGS;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PURGE BINARY LOGS TO 'binlog.00000x';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Thats it. You have fixed the errant transaction. This was a rather simple example of an errant GTID. I will be doing part 2 that will look at more complexed examples.
&lt;/p>
&lt;h3 id="referance-information">Referance Information&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/set-sql-log-bin.html" target="_blank" rel="noopener noreferrer">Set SQL Log Bin&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/gtid-functions.html" target="_blank" rel="noopener noreferrer">GTID Functions&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Wayne Leutwyler</author><category>Percona</category><category>MySQL</category><category>Recovery</category><category>Replication</category><category>GTID</category><media:thumbnail url="https://percona.community/blog/2021/11/errant-gtid-1_hu_ee112b4f2df8b1e3.jpg"/><media:content url="https://percona.community/blog/2021/11/errant-gtid-1_hu_34baad6223fe5844.jpg" medium="image"/></item><item><title>Going back to the original node_exporter in PMM</title><link>https://percona.community/blog/2021/10/21/going-back-to-original-pmm-node-exporter/</link><guid>https://percona.community/blog/2021/10/21/going-back-to-original-pmm-node-exporter/</guid><pubDate>Thu, 21 Oct 2021 15:00:00 UTC</pubDate><description>This is my first (I hope) post about something no so usual in our regular posts about technology.</description><content:encoded>&lt;p>This is my first (I hope) post about something no so usual in our regular posts about technology.&lt;/p>
&lt;p>Usually we discuss new features, talk about how to do something but even for me, a Percona developer, sometimes it is hard to know where and what to touch in PMM. There are many components, many abstractions, parts that send messages to remote APIs or agents, the PMM agent, the PMM API (pmm-managed), the command line client (pmm-admin) and all the external exporters.&lt;/p>
&lt;p>In this post, I will try to show how to implement the replacement of the current node_exporter we use in PMM to move back to the original one.&lt;/p>
&lt;h2 id="what-this-post-is-about">What this post is about?&lt;/h2>
&lt;p>In the next paragraphs, I’ll try to explain the basics of how PMM works. My intention is to walk you trough the internals of the PMM API and PMM agent, how do the communicate and how to make some code changes.
There are many places to contact us to get help if you need to, but nowadays, &lt;a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">forums.percona.com&lt;/a> is the fastest place to get answers.
I will try to keep things clear and simple, but this is a technical post so, there will be some code.&lt;/p>
&lt;h2 id="why-do-we-use-a-different-node_exporter">Why do we use a different node_exporter?&lt;/h2>
&lt;p>Probably going back in time we could find many other reasons, like maintainability, or the ability to use custom builds but one of the things that lacked in the first exporters was the support for basic authentication. In PMM, all exporters metrics are password protected and since there was no support for that in the past and we needed it as part of our specification, PMM exporters use a common HTTP module called &lt;code>exporter_shared&lt;/code>. In that module, the HTTP server supports basic authentication and some other features as well but time has passed, Prometheus exporters are much much mature and now the Prometheus &lt;a href="https://github.com/prometheus/exporter-toolkit/tree/v0.1.0/https" target="_blank" rel="noopener noreferrer">exporter-toolkit package&lt;/a> has support for TLS, HTTP2, cyphers, basic auth, etc.&lt;/p>
&lt;h2 id="how-pmm-works">How PMM works.&lt;/h2>
&lt;p>As mentioned before, there are many components in PMM. The one in charge to start internal and external exporters and run commands is &lt;code>pmm-agent&lt;/code>. Internal exporters are the ones built in into &lt;code>pmm-agent&lt;/code>, mostly for Query Analytics and running commands like &lt;code>EXPLAIN&lt;/code>, &lt;code>SHOW TABLES&lt;/code>, etc.
Also, &lt;code>pmm-agent&lt;/code> has an internal &lt;code>supervisor&lt;/code> that, like the popular Python’s &lt;a href="http://supervisord.org/" target="_blank" rel="noopener noreferrer">supervisord&lt;/a> project, run processes (agents) and manage them.&lt;/p>
&lt;p>How does pmm-agent know which parameters should be used to run each exporter? That’s where &lt;code>pmm-managed&lt;/code> gets involved. &lt;code>pmm-managed&lt;/code> is the PMM API server and it is the one that sends and receive commands from the UI or the command line client, prepare the messages and deliver them to the proper &lt;code>pmm-agent&lt;/code>.&lt;/p>
&lt;p>As a general rule, all agents are defined in &lt;code>pmm-managed&lt;/code>’s &lt;code>services/agents&lt;/code> directory.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="436" height="554" sizes="(max-width: 436px) 100vw, 436px" srcset="https://percona.community/../assets/blog/2021/10/directory_hu_af3f67ce7ebeade7.webp 436w" src="https://percona.community/../assets/blog/2021/10/directory_hu_af3f67ce7ebeade7.webp" alt="directory" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In our case, we want to modify how we start the &lt;code>node_exporter&lt;/code> so we need to modify the &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/node.go" target="_blank" rel="noopener noreferrer">services/agents/node.go&lt;/a> file.
The &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/node.go#L31" target="_blank" rel="noopener noreferrer">nodeExporterConfig&lt;/a> is defined as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">nodeExporterConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">models&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Node&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">exporter&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">models&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Agent&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">agentpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SetStateRequest_AgentProcess&lt;/span> &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>and the returned structure is defined in the pmm repository which has all the definitions for PMM.
The &lt;a href="https://github.com/percona/pmm/blob/PMM-2.0/api/agentpb/agent.proto#L54-L62" target="_blank" rel="noopener noreferrer">AgentProcess&lt;/a> message has these fields:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">message&lt;/span> &lt;span class="nx">AgentProcess&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">inventory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AgentType&lt;/span> &lt;span class="kd">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="nx">template_left_delim&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="nx">template_right_delim&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">repeated&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="nx">args&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">repeated&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="nx">env&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">map&lt;/span>&lt;span class="p">&lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">>&lt;/span> &lt;span class="nx">text_files&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">repeated&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="nx">redact_words&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Currently, our node_exporter fork receives the user name and password used for the exporter’s basic auth via the &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/node.go#L135-L137" target="_blank" rel="noopener noreferrer">HTTP_AUTH&lt;/a> environment var:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Env&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"HTTP_AUTH=pmm:%s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">exporter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AgentID&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>From the Prometheus exporter-toolkit package, we can see it receives the configuration information via a file specified with the &lt;code>--web.config&lt;/code> parameter and the config example tell us we also need to encrypt the password&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Usernames and hashed passwords that have full access to the web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># server via basic authentication. If empty, no basic authentication is&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># required. Passwords are hashed with bcrypt.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">basic_auth_users&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">[ &lt;string>&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">&lt;secret> ... ]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>so, we need to update the &lt;code>nodeExporterConfig&lt;/code> function to:&lt;/p>
&lt;ol>
&lt;li>Update the parameters sent to &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/node.go#L130-L138" target="_blank" rel="noopener noreferrer">pmm-agent&lt;/a> to make the exporter receive the new configuration file.&lt;/li>
&lt;li>Update the node config response to include the new files and remove unused env vars.&lt;/li>
&lt;li>Last but not least, update the tests.&lt;/li>
&lt;/ol>
&lt;p>In first place, we need to create a new configuration file and make the&lt;code>node_expoter&lt;/code>use but, how? The node exporter runs on the client server but pmm-managed runs on pmm server so, at first glance, it is not as easy as writing a file and updating the parameters but it is. We can spy on other exporter’s config definition to see how are they receiving the TLS certificate files and we can do the same for the web.config file. Let’s take a look at the mysql_exporter.&lt;/p>
&lt;p>The &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/mysql.go#L33" target="_blank" rel="noopener noreferrer">mysqlExporterConfig&lt;/a> method returns all the paremeters needed to call the node exporter. The &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/mysql.go#L131" target="_blank" rel="noopener noreferrer">TextFiles&lt;/a> parameter is being built &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/services/agents/mysql.go#L100-L113" target="_blank" rel="noopener noreferrer">here&lt;/a> and for each file there is an exporter’s file parameter. For example, for the &lt;code>--mysql.ssl-ca-file=&lt;/code> parameter it receives:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tdp.Left+" .TextFiles.tlsCa "+tdp.Right&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This might look complicates but &lt;strong>tdp&lt;/strong> stands for &lt;strong>T&lt;/strong>emplate &lt;strong>D&lt;/strong>elimiter &lt;strong>P&lt;/strong>air and it is just a helper to choose the correct delimiters in case this value id used in a template and the rest is just a parameter, in this case, the TLS CA file (the file contents, not just the name).&lt;/p>
&lt;h2 id="implementing-the-changes">Implementing the changes.&lt;/h2>
&lt;h3 id="1-return-files-for-node_exporter-type">1. Return files for node_exporter type&lt;/h3>
&lt;p>The current node_exporter in PMM is a fork that doesn’t use the exporter-toolkit package for the http server, it uses Percona’s exporter_shared instead so, to make the upstream exporter behave like the forked one, we need to create a pass a file for the &lt;code>--web.config&lt;/code> parameter having the Basic Authentication parameters we use to protect the metrics exposition.&lt;/p>
&lt;p>In pmm-managed agent_model’s &lt;a href="https://github.com/percona/pmm-managed/blob/PMM-2.0/models/agent_model.go#L562" target="_blank" rel="noopener noreferrer">Files()&lt;/a> function, we need to return the list of files for &lt;code>NodeExporterType&lt;/code> and we are going to write a new function to build the config file (&lt;code>buildWebConfigFile&lt;/code>).&lt;/p>
&lt;p>&lt;code>webConfigFilePlaceholder&lt;/code> is just a string constant used to identify the different files that can be passed to the agents.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">const (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // webConfigFile is the Prometheus HTTP Toolkit's web.config file.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // It the basic auth parameters we need to set for node exporter.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // All other exporters are using exporter shared but after going back to the upstream
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // version of node_exporter, we need to pass this file to pmm-agent.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> webConfigFilePlaceholder = "webConfigPlaceholder"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> case NodeExporterType:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return map[string]string{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> webConfigFilePlaceholder: s.buildWebConfigFile(s.GetAgentPassword()),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>buildWebConfigFile&lt;/code> returns the file content needed to specify user and password for the exporter’s basic auth.&lt;/p>
&lt;p>According to the &lt;a href="https://github.com/prometheus/exporter-toolkit/tree/v0.1.0/https" target="_blank" rel="noopener noreferrer">documentation&lt;/a>,the password must be encrypted so, our function receives a plain-test password and return the configuration file contents with the password encrypted with bcrypt.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">func (s *Agent) buildWebConfigFile(password string) string {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> buf, err := bcrypt.GenerateFromPassword([]byte(password), 14)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if err != nil {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.Fatal(err, "cannot encrypt basic auth password")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> content := "basic_auth_users:" + "\n" +
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> " pmm:" + string(buf) + "\n"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return content
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="2-update-the-node-service">2. Update the node service&lt;/h3>
&lt;p>In &lt;code>services/agents/node.go&lt;/code> there is a &lt;code>nodeExporterConfig&lt;/code> which is called to get the exporter configuration.&lt;/p>
&lt;p>The new implementation should get the files the exporter is going to use and remove the now unused environment variables.&lt;/p>
&lt;p>The code will look like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> files := exporter.Files()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k := range files {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> switch k {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> case "webConfigPlaceholder":
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // see https://github.com/prometheus/exporter-toolkit/tree/v0.1.0/https
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> args = append(args, "--web.config="+tdp.Left+" .TextFiles.webConfigPlaceholder "+tdp.Right)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> default:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> continue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sort.Strings(args)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return &amp;agentpb.SetStateRequest_AgentProcess{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Type: inventorypb.AgentType_NODE_EXPORTER,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> TemplateLeftDelim: tdp.Left,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> TemplateRightDelim: tdp.Right,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Args: args,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Env: []string{},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> TextFiles: files,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="3-updating-tests">3. Updating tests&lt;/h3>
&lt;p>Since we changed the response for the &lt;code>nodeExporterConfig&lt;/code> method and now we are returning files and removed the environment vars, tests will fail.&lt;/p>
&lt;p>We need to update the tests at &lt;code>services/agents/node_test.go&lt;/code> to reflect the changes. I am not going to get into the details because they are trivial but I do want to mention that since the encrypted password is not always the same (because of the nature of the function) I am just comparing that we are returning a file from the Files() method (&lt;code>agent_model.go&lt;/code>).&lt;/p>
&lt;p>If you never ran the test for pmm-managed let me quickly show the the only 2 steps needed:&lt;/p>
&lt;ol>
&lt;li>make env-up&lt;/li>
&lt;li>make env TARGET=test&lt;/li>
&lt;/ol>
&lt;p>With those 2 commands, you will be able to run all the tests in the suite to ensure the changes won’t break anything.&lt;/p>
&lt;p>I hope I was able to explain at least the basics about how to make changes in PMM.
Remember you can contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> ans we will be glad to answer your questions.&lt;/p></content:encoded><author>Carlos Salguero</author><category>node_exporter</category><category>exporter</category><category>pmm</category><media:thumbnail url="https://percona.community/assets/blog/2021/10/directory_hu_4c042915442984b0.jpg"/><media:content url="https://percona.community/assets/blog/2021/10/directory_hu_767ada9cff1b08e9.jpg" medium="image"/></item><item><title>2.23.0 Preview Release(Updated!)</title><link>https://percona.community/blog/2021/10/15/preview-release/</link><guid>https://percona.community/blog/2021/10/15/preview-release/</guid><pubDate>Fri, 15 Oct 2021 00:00:00 UTC</pubDate><description>Update Percona Monitoring and Management 2.23.0 is now available as a Public Release!</description><content:encoded>&lt;h2 id="update">Update&lt;/h2>
&lt;p>Percona Monitoring and Management 2.23.0 is now available as a Public Release!&lt;/p>
&lt;p>Release notes for Percona Monitoring and Management 2.23.0 Public Release can be found &lt;a href="https://per.co.na/pmm/2.23.0" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h2 id="2230-preview-release">2.23.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.23.0 is released today as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Internal Release in &lt;strong>testing environments&lt;/strong> only, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Known issue:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PMM-8983" target="_blank" rel="noopener noreferrer">PMM-8983&lt;/a> - DBaaS: PXC cluster is displayed as active after suspend&lt;/li>
&lt;/ul>
&lt;p>Release notes:
Release Notes Preview found &lt;a href="https://deploy-preview-610--pmm-doc.netlify.app/release-notes/2.23.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag: &lt;a href="https://hub.docker.com/layers/percona/pmm-server/2.23.0/images/sha256-ff0bb20cba0dbfcc8929dbbba0558bb01acc933ec593717727707dce083441b4?context=explore" target="_blank" rel="noopener noreferrer">percona/pmm-server:2.23.0&lt;/a>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.23.0 from this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-latest-3126.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable original testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3-website-us-east-1.amazonaws.com/PMM2-Server-2021-10-14-2120.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2021-10-14-2120.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html&lt;/a>&lt;/p>
&lt;p>Artifact id: &lt;code>ami-047173e7a14c3f287&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Taras Kozub</author><category>PMM</category><media:thumbnail url="https://percona.community/blog/2021/10/super_hero_sloth_hu_8a1e547f9c5b81d0.jpg"/><media:content url="https://percona.community/blog/2021/10/super_hero_sloth_hu_c66f52f5f7da3ea3.jpg" medium="image"/></item><item><title>2.22.0 Preview Release</title><link>https://percona.community/blog/2021/09/16/preview-release/</link><guid>https://percona.community/blog/2021/09/16/preview-release/</guid><pubDate>Thu, 16 Sep 2021 00:00:00 UTC</pubDate><description>2.20.0 Preview Release Percona Monitoring and Management 2.22.0 is released today as a Preview Release.</description><content:encoded>&lt;h2 id="2200-preview-release">2.20.0 Preview Release&lt;/h2>
&lt;p>Percona Monitoring and Management 2.22.0 is released today as a Preview Release.&lt;/p>
&lt;p>PMM team really appreciates your feedback!&lt;/p>
&lt;p>We encourage you to try this PMM Preview Release in &lt;strong>testing environments&lt;/strong> only, as these packages and images are not fully production-ready. The final version is expected to be released through the standard channels in the coming week.&lt;/p>
&lt;p>Known issue:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PMM-8829" target="_blank" rel="noopener noreferrer">PMM-8829&lt;/a> - “Missing Listen Port” error for external exporters after restart&lt;/li>
&lt;/ul>
&lt;p>Release notes:
Release Notes Preview found &lt;a href="https://deploy-preview-588--pmm-doc.netlify.app/release-notes/2.22.0.html" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/p>
&lt;h3 id="pmm-server-docker">PMM server docker&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/docker.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>docker tag: &lt;code>perconalab/pmm-server:2.22.0-rc&lt;/code>&lt;/p>
&lt;p>&lt;a href="https://hub.docker.com/layers/perconalab/pmm-server/2.22.0-rc/" target="_blank" rel="noopener noreferrer">https://hub.docker.com/layers/perconalab/pmm-server/2.22.0-rc/&lt;/a>&lt;/p>
&lt;h3 id="pmm-client-package-installation">PMM client package installation&lt;/h3>
&lt;p>Download the latest pmm2-client Release Candidate tarball for 2.22.0 from this &lt;a href="https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-PR-2003-7917413.tar.gz" target="_blank" rel="noopener noreferrer">link&lt;/a>.&lt;/p>
&lt;p>If you want to install pmm2-client package, please enable testing repository via Percona-release:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">percona-release enable original testing&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>install pmm2-client package for your OS via package manager.&lt;/p>
&lt;h3 id="ova">OVA&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/virtual-appliance.html" target="_blank" rel="noopener noreferrer">Instructions&lt;/a>&lt;/p>
&lt;p>Artifact: &lt;a href="http://percona-vm.s3-website-us-east-1.amazonaws.com/PMM2-Server-2021-09-14-1514.ova" target="_blank" rel="noopener noreferrer">PMM2-Server-2021-09-14-1514.ova&lt;/a>&lt;/p>
&lt;h3 id="ami">AMI&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html" target="_blank" rel="noopener noreferrer">https://www.percona.com/doc/percona-monitoring-and-management/2.x/setting-up/server/aws.html&lt;/a>&lt;/p>
&lt;p>Artifact id: &lt;code>ami-0a6b861c9225afbd8&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Please also check out our Engineering Monthly Meetings &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Denys Kondratenko</author><category>PMM</category><media:thumbnail url="https://percona.community/superhero_hu_252fc2b480c0a197.jpg"/><media:content url="https://percona.community/superhero_hu_17979f11d5d3562e.jpg" medium="image"/></item><item><title>The lost art of Database Server Initialization.</title><link>https://percona.community/blog/2021/09/06/lost-art-of-database-server-initialization/</link><guid>https://percona.community/blog/2021/09/06/lost-art-of-database-server-initialization/</guid><pubDate>Mon, 06 Sep 2021 00:00:00 UTC</pubDate><description>With all the DBaaS, IaaS and PaaS environments, sometimes I think the Art of MySQL initialization is becoming a lost art. Many times we just delete the MySQL Server and order a new one.</description><content:encoded>&lt;p>With all the DBaaS, IaaS and PaaS environments, sometimes I think the Art of MySQL initialization is becoming a lost art. Many times we just delete the MySQL Server and order a new one.&lt;/p>
&lt;p>Just recently I was talking with a colleague, and this subject came up. We both thought about it and decided we have become spoiled by automation. We were both rusty on this process. This gave me the idea for this post.
&lt;figure>&lt;img width="325" height="382" sizes="(max-width: 325px) 100vw, 325px" srcset="https://percona.community/blog/2021/09/lostart-01_hu_ce974887c8a756e0.webp 325w" src="https://percona.community/blog/2021/09/lostart-01_hu_ce974887c8a756e0.webp" alt="lostart-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You might be thinking why initialize MySQL again? Let’s say that you wanted MySQL Server 8.0 not to use mixed case. Yet when the database was initialized the default setting of &lt;code>lower_case_table_names = 0&lt;/code> was used. With 8.0 you can’t make the change &lt;code>lower_case_table_names = 1&lt;/code> in the my.cnf and restart MySQL. It won’t work, leaving you with two option. One Initialize MySQL a second time, or order a new environment.&lt;/p>
&lt;p>Let’s look at the steps we would need to change the MySQL server to support only lower case.&lt;/p>
&lt;p>You may want to take a backup before you begin these steps if you have already loaded data that you wish to keep.&lt;/p>
&lt;h2 id="the-steps">The Steps&lt;/h2>
&lt;p>The steps below assume you are working with a default MySQL
server installation. Modify as needed for a custom installation. One word of caution. Please dont user root to run the bellow commands. Use &lt;code>sudo mysql&lt;/code> this will add an extra layer of safety by not being root.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Stop the MySQL Server. &lt;code>$ systemctl stop mysqld&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>You will need to delete everything out of your current data directory.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ rm -fR /var/lib/mysql*&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Edit your my.cnf file and add: &lt;code>lower_case_table_names=1&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vi /etc/my.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="818" height="248" sizes="(max-width: 818px) 100vw, 818px" srcset="https://percona.community/blog/2021/09/lostart-02_hu_69fc47f078d1478a.webp 768w, https://percona.community/blog/2021/09/lostart-02_hu_c655ce5dfa95e97c.webp 480w, https://percona.community/blog/2021/09/lostart-02_hu_f227f49afaa2089e.webp 818w" src="https://percona.community/blog/2021/09/lostart-02_hu_f227f49afaa2089e.webp" alt="lostart-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Now initialize mysql.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ /usr/sbin/mysqld --initialize --user=mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Get the temporary root password from the &lt;code>mysqld.log&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cat /var/log/mysqld.log | grep password&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2244" height="114" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2021/09/lostart-03_hu_73137aa74d91eae1.webp 768w, https://percona.community/blog/2021/09/lostart-03_hu_7e34e3ea14ce2bc7.webp 480w, https://percona.community/blog/2021/09/lostart-03_hu_cb7d8246dfdc16ba.webp 1400w" src="https://percona.community/blog/2021/09/lostart-03_hu_cb7d8246dfdc16ba.webp" alt="lostart-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you dont find the temporary password for the root user, review the steps above making sure you did not miss something.&lt;/p>
&lt;ol start="6">
&lt;li>Start MySQL.
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ systemctl start mysqld&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>Verify MySQL is running.
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ cat /var/log/mysqld.log&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="915" height="76" sizes="(max-width: 915px) 100vw, 915px" srcset="https://percona.community/blog/2021/09/lostart-04_hu_d96014af364545e8.webp 768w, https://percona.community/blog/2021/09/lostart-04_hu_30b53b7d6df76565.webp 480w, https://percona.community/blog/2021/09/lostart-04_hu_5b3578529f0c97eb.webp 915w" src="https://percona.community/blog/2021/09/lostart-04_hu_5b3578529f0c97eb.webp" alt="lostart-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now you should be able to log into MySQL using the password received got from step 5.&lt;/p>
&lt;p>There could be many more reasons to re-initliatize a MySQL Database. This is just one example.
Automation is great. Just remember to pull out your command line tools now and then, so they dont get to rusty.&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>Percona</category><category>MySQL</category><category>Recovery</category><category>Installation</category><media:thumbnail url="https://percona.community/blog/2021/09/lostart-01_hu_62162eb2becac880.jpg"/><media:content url="https://percona.community/blog/2021/09/lostart-01_hu_6c77c8b753b5f4a8.jpg" medium="image"/></item><item><title>Humans need not apply</title><link>https://percona.community/blog/2021/08/19/humans-need-not-apply-tarantool-ansible/</link><guid>https://percona.community/blog/2021/08/19/humans-need-not-apply-tarantool-ansible/</guid><pubDate>Thu, 19 Aug 2021 00:00:00 UTC</pubDate><description>Hi, my name is Roman Proskin. I work at Mail.Ru Group and develop high-performance applications on Tarantool, which is an in-memory computing platform.</description><content:encoded>&lt;p>Hi, my name is Roman Proskin. I work at Mail.Ru Group and develop high-performance applications on Tarantool, which is an in-memory computing platform.&lt;/p>
&lt;p>In this article, I will explain how we built the automated process of deploying Tarantool apps. It allows updating the codebase in production without any downtime or outages. I will describe the problems we faced and the solutions we found in the process. I hope that &lt;em>our&lt;/em> experience will be useful for &lt;em>your&lt;/em> deployments.&lt;/p>
&lt;p>It’s not that hard to deploy an application. Our &lt;strong>cartridge-cli&lt;/strong> tool (&lt;a href="https://github.com/tarantool/cartridge-cli" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>). lets you deploy cluster applications within a couple of minutes – for instance, in Docker. However, it is &lt;em>much&lt;/em> harder to turn a small-scale solution into a full-fledged product, one that would handle hundreds of instances and be used by dozens of teams of various levels.&lt;/p>
&lt;p>Our deployment is based on a simple idea: set up two hardware servers, run an instance on each server, join the instances in a single replica set, and update them one by one. However, when it comes to deploying a production system with terabytes of unique data — palms are sweaty, knees weak, arms are heavy, there’s vomit on the sweater already, code’s spaghetti. The database might be on the verge of collapse.&lt;/p>
&lt;h2 id="initial-conditions">Initial Conditions&lt;/h2>
&lt;p>There is a strict SLA for the project: 99% uptime is required, planned downtime included in the remaining 1%. This means that there are 87 hours each year when we are allowed to &lt;em>not respond&lt;/em> to requests. It seems like a big number, &lt;em>but&lt;/em>…&lt;/p>
&lt;p>The project is targeting about 1.8 TB of data, so a mere restart would take as much as 40 minutes! Add the time for the manual update itself on top of that. Three updates a week take 40*3*52/60 = &lt;strong>104 hours&lt;/strong>, &lt;em>which breaks the SLA&lt;/em>. And this is only &lt;em>planned&lt;/em> maintenance work. What about the outages that are surely going to happen?&lt;/p>
&lt;p>As the application is designed with heavy user load in mind, it has to be very stable. We don’t want to lose data if a node dies. So we divided our cluster geographically, using machines in two data centers. This deployment mechanism makes sure that the SLA is not violated. Updates are rolled out on groups of instances in different data centers, not on all of them at once. During the updates, we transfer the load to the other data center, so the cluster remains writable. This classical deployment strategy is a standard disaster recovery practice.**&lt;/p>
&lt;p>One of the key elements of downtime-free deployment is the ability to update instances one data center at a time. I will explain more about that process by the end of the article. Now, let’s focus on our automated deployment and the challenges associated with it.&lt;/p>
&lt;h2 id="challenges">Challenges&lt;/h2>
&lt;h3 id="moving-traffic-across-the-street">Moving Traffic Across the Street&lt;/h3>
&lt;p>There are several data centers and requests may hit any of them. Retrieving data from another data center increases the response time by 1–100 milliseconds. To avoid cross-user-traffic between the two data centers, we tagged them as &lt;em>active&lt;/em> and &lt;em>standby&lt;/em>. We configured the &lt;strong>nginx&lt;/strong> balancer so that all the traffic would always be directed to the active data center. If Tarantool in the active data center failed or became unavailable, the traffic would go to the standby data center instead.&lt;/p>
&lt;p>Every user request matters, so we needed to ensure that every connection would be maintained. For that we wrote a special Ansible playbook that switches the traffic between the data centers. The switch is implemented through the &lt;code>backup&lt;/code> option of every server’s &lt;code>upstream&lt;/code> directive. The servers that have to become active are defined with the ansible-playbook &lt;code>--limit&lt;/code> flag. The other servers are marked as &lt;code>backup&lt;/code>, and nginx will only direct traffic at them if &lt;em>all active servers&lt;/em> are unavailable. If there are open connections during a configuration change, they will not be closed, and &lt;em>new&lt;/em> requests will be redirected to the routers that haven’t been restarted because of the change.&lt;/p>
&lt;p>What if there is no external balancer in the infrastructure? You can write your own balancer in Java to monitor the availability of Tarantool instances. However, that separate subsystem also requires deployment. Another option is to embed the switch mechanism in the routers. Whatever the case may be, you have to control HTTP traffic.&lt;/p>
&lt;p>OK, we configured nginx, but this is not our only challenge. We also have to rotate masters in replica sets. As I mentioned above, data &lt;em>must&lt;/em> be kept close to the routers to avoid external retrievals whenever possible. Moreover, if the current master (the writable storage instance) dies, the failover mechanism is not launched instantly. First, the cluster has to come to a group decision to declare the instance unavailable. During that time, all requests to the data in question fail. To solve this problem, we developed another playbook that sends GraphQL requests to the cluster API.&lt;/p>
&lt;p>The mechanisms to rotate masters and switch user traffic are two remaining key elements of downtime-free deployment. A controlled balancer helps avoid connection loss and user request processing errors. Master rotation helps eliminate data access errors. These techniques, along with branch-wise updates, form the three pillars of failsafe deployment, which we later automated.&lt;/p>
&lt;h3 id="legacy-strikes-back">Legacy Strikes Back&lt;/h3>
&lt;p>Our client had a custom deployment solution – Ansible roles with step-by-step instance deployment and configuration. Then we arrived with the magic &lt;strong>ansible-cartridge&lt;/strong> (&lt;a href="https://github.com/tarantool/ansible-cartridge" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>) that solves all the problems. We just didn’t factor in that ansible-cartridge is a monolith. It is a single huge role with a lot of stages, divided into smaller tasks and marked all over by tags.&lt;/p>
&lt;p>To use ansible-cartridge efficiently, we had to alter the process of artifact delivery, reconsider directory structure on target machines, switch to a different orchestrator, and make other changes. I spent a whole month on improving the deployment solution with ansible-cartridge. But the monolithic role just didn’t fit in with the existing custom playbooks. While I was struggling to make use of the role that way, my colleague asked me a fair question, “Do we even want that?”&lt;/p>
&lt;p>So we found another way – we put cluster configuration into a separate playbook. Specifically, that playbook was responsible for joining storage instances into replica sets, vshard (cluster data sharding mechanism) bootstrapping, and failover configuration (automated master rotation in case of death).**** These are the final stages of deployment that take place when all the instances are already running.&lt;/p>
&lt;p>Unfortunately, we had to keep all the other deployment stages unchanged.&lt;/p>
&lt;h3 id="choosing-an-orchestrator">Choosing an Orchestrator&lt;/h3>
&lt;p>If the code on the servers can’t be launched, it’s useless. We needed a utility to start and stop Tarantool instances. There are tasks in ansible-cartridge that can create systemctl service files and work with RPM packages. However, our client had a closed network and no sudo privileges, which meant that we could not use systemctl.&lt;/p>
&lt;p>Soon we found &lt;strong>supervisord&lt;/strong>, an orchestrator that didn’t require root privileges all the time. We had to pre-install it on all the servers and solve local problems with socket file access. To set up supervisord, we wrote a separate Ansible role, which created configuration files, updated the configuration, launched and stopped instances. That was enough to roll out into production.&lt;/p>
&lt;p>Supervisord application launch was added to ansible-cartridge for the sake of experiment. That method, however, proved less flexible and is currently awaiting improvement in a designated branch.&lt;/p>
&lt;h3 id="reducing-load-time">Reducing Load Time&lt;/h3>
&lt;p>Whatever orchestrator we use, we cannot wait for an hour every time an instance has to boot. The threshold is 20 minutes. If an instance remains unavailable for a longer time, it will be reported to the incident management system. Frequent failures impact team KPIs and may sabotage system development plans. We didn’t want to lose our bonus because of a scheduled deployment, so we needed to keep the boot time within 20 minutes at all costs.&lt;/p>
&lt;p>Here is a fact: load time directly correlates with the amount of data. The more information has to be restored from the disk into the RAM, the longer it takes for the instance to start after an update. Consider also that storage instances on one machine will compete for resources, as Tarantool builds indexes using all processor cores.&lt;/p>
&lt;p>Our observations show that an instance’s memtx_memory must not exceed 40 GB. This optimal size makes sure that instance recovery takes less than 20 minutes. The number of instances on a server is calculated separately and closely linked to the project infrastructure.&lt;/p>
&lt;h3 id="setting-up-monitoring">Setting Up Monitoring&lt;/h3>
&lt;p>Every system, including Tarantool, has to be monitored. However, we did not set up monitoring right away. &lt;strong>It took us three months to obtain access rights, get approvals, and configure the environment.&lt;/strong>&lt;/p>
&lt;p>**While developing our application and writing playbooks, we touched up the &lt;strong>metrics&lt;/strong> module (&lt;a href="https://github.com/tarantool/metrics" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>). Global labels now allow separating out metrics by instance name. We also developed a special &lt;a href="https://github.com/tarantool/metrics#cartridge-role" target="_blank" rel="noopener noreferrer">role&lt;/a> to integrate Tarantool cluster application metrics with monitoring systems. Besides, we introduced a new useful metric, &lt;a href="https://habr.com/ru/company/mailru/blog/529456/" target="_blank" rel="noopener noreferrer">&lt;em>quantile&lt;/em>&lt;/a>.&lt;/p>
&lt;p>Now we can see the current number of requests to the system, memory usage data, the replication lag, and many other key metrics. Chat notification alerts are set up for all of them. The incident management system records critical issues, and there is a strict SLA for resolving them.&lt;/p>
&lt;p>Let’s talk more about our monitoring tools. &lt;strong>etcd&lt;/strong> specifies the logs to collect and provides a full description of where and how to collect them, and the &lt;strong>telegraf&lt;/strong> agent takes its cues from there. JSON metrics are stored in &lt;strong>InfluxDB&lt;/strong>. We visualized data with &lt;strong>Grafana&lt;/strong> and even created a &lt;a href="https://grafana.com/grafana/dashboards/13054" target="_blank" rel="noopener noreferrer">dashboard&lt;/a> template for it. Finally, alerts are configured with &lt;strong>kapacitor&lt;/strong>.&lt;/p>
&lt;p>Of course, this is not the only monitoring implementation that works. You can use &lt;strong>Prometheus&lt;/strong>, especially since the metrics module can yield values in a compatible format. Alerts can be also configured with &lt;strong>Zabbix&lt;/strong>.&lt;/p>
&lt;p>To learn more about Tarantool monitoring setup, read my colleague’s article &lt;a href="https://habr.com/ru/company/mailru/blog/534826/" target="_blank" rel="noopener noreferrer">Tarantool Monitoring: Logs, Metrics, and Their Processing&lt;/a>.&lt;/p>
&lt;h3 id="enabling-logging">Enabling Logging&lt;/h3>
&lt;p>Simply monitoring the system is not enough. To see the big picture, you have to collect all diagnostic insights, including logs. Higher logging levels yield more debug information but also produce larger log files.&lt;/p>
&lt;p>However, disk space is finite. At peak load, our application could generate up to 1 TB of logs per day. Of course, we could add more disks, but sooner or later, we would run out of either free space or project budget. Yet we didn’t want to wipe debug information completely. So what did we do?&lt;/p>
&lt;p>One of the stages of our deployment was to configure &lt;strong>logrotate&lt;/strong>. It allowed us to store a couple of 100 MB uncompressed log files and a couple more compressed ones, which is enough to pinpoint a local issue within 24 hours under normal operations. The logs are stored in a designated directory in JSON format. All the servers are running the &lt;strong>filebeat&lt;/strong> daemon, which collects application logs and sends them to &lt;strong>ElasticSearch&lt;/strong> for long-term storage. This approach helps avoid disk overflow errors and allows analyzing system operations in case of persistent problems. It also integrates well with the deployment.&lt;/p>
&lt;h3 id="scaling-the-solution">Scaling the Solution&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="2558" height="1438" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2021/08/human-2_hu_28e8e75b82f987d4.webp 768w, https://percona.community/blog/2021/08/human-2_hu_e4f8ad8372b391a1.webp 480w, https://percona.community/blog/2021/08/human-2_hu_21b2186afe888e1d.webp 1400w" src="https://percona.community/blog/2021/08/human-2_hu_21b2186afe888e1d.webp" alt="Scaling the Solution" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Our path was a long and rocky one, and we learned a lot by trial and error. To avoid making the same mistakes, we standardized our deployment, relying on the CI/CD formula of Gitlab + Jenkins. Scaling was also challenging – it took us months to debug our solution. Still, we tackled all the problems and are now ready to share our experience with you. Let’s do it step by step.&lt;/p>
&lt;p>How do we make sure that any developer can quickly put together a solution for their problem and deliver it to production? Take the Jenkinsfile away from them! We have to set firm boundaries and disallow deployment if the developer violates them. We created and rolled out to production a full-fledged example application, which serves as the perfect zero app. With our client, we went even further and wrote a utility for template creation that automatically configures the Git repo and Jenkins jobs. As a result, the developer needs less than an hour to get ready and push their project to production.&lt;/p>
&lt;p>The pipeline begins with standard code checkout and environment setup. We add inventories for further deployment to a number of test zones and to production. Then the unit tests begin.&lt;/p>
&lt;p>We use the standard Tarantool &lt;strong>luatest&lt;/strong> framework (&lt;a href="https://github.com/tarantool/luatest" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>), which allows writing both unit and integration tests. It also has modules for launching and configuring &lt;a href="https://www.tarantool.io/en/doc/latest/getting_started/getting_started_cartridge/" target="_blank" rel="noopener noreferrer">Tarantool Cartridge&lt;/a>. Code coverage checking can be enabled in the most recent versions of luatest.** To run it, execute the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.rocks/bin/luatest --coverage&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After the tests are over, the statistical data is sent to &lt;strong>SonarQube&lt;/strong>, a piece of software for code quality assurance and security checking. We have a Quality Gate configured inside it. Any code in the application, regardless of the language (Lua, Python, SQL, etc.), is subject to checking. However, SonarQube lacks a built-in Lua processor. Therefore, to provide coverage in a generic format, we have to install special modules before the tests.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantoolctl rocks install luacov 0.13.0-1 # coverage collection utility
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantoolctl rocks install luacov-reporters 0.1.0-1 # additional reports&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To get a simple console view, execute:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.rocks/bin/luacov -r summary . &amp;&amp; cat ./luacov.report.out&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To form a SonarQube report, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.rocks/bin/luacov -r sonar .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After that, the linter is launched. We use &lt;strong>luacheck&lt;/strong> (&lt;a href="https://github.com/mpeterv/luacheck" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>), which is also a Tarantool module.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantoolctl rocks install luacheck 0.26.0-1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Linter results are also sent to SonarQube.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">.rocks/bin/luacheck --config .luacheckrc --formatter sonar *.lua&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Code coverage and linter statistics are both taken into account. To pass the Quality Gate, all of the following must be true:&lt;/p>
&lt;ul>
&lt;li>Code coverage is no less than 80%.&lt;/li>
&lt;li>The changes do not introduce any new code smells.&lt;/li>
&lt;li>There are 0 critical errors in total.&lt;/li>
&lt;li>There are no more than 5 minor errors.&lt;/li>
&lt;/ul>
&lt;p>After the code passes the Quality Gate, we have to assemble the artifact. As we decided that all applications would be using Tarantool Cartridge, we build the artifact with &lt;strong>cartridge-cli&lt;/strong> (&lt;a href="https://github.com/tarantool/cartridge-cli" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>). This small utility lets you run (in fact, develop) Tarantool cluster applications locally. It can also create Docker images and archives from application code, both locally and in Docker – for instance, if you have to build an artifact for a different infrastructure. To assemble a &lt;code>tar.gz&lt;/code> archive, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">cartridge pack tgz --name &lt;name> --version &lt;version>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The resulting archive can then be uploaded to any repository, like &lt;strong>Artifactory&lt;/strong> or &lt;a href="https://mcs.mail.ru/storage/" target="_blank" rel="noopener noreferrer">&lt;strong>Mail.ru Cloud Storage&lt;/strong>&lt;/a>.&lt;/p>
&lt;h2 id="downtime-free-deployment">Downtime-free Deployment&lt;/h2>
&lt;p>The final step of the pipeline is deployment itself. The code is deployed to one of several test zones based on branch merge status. One zone is designated for testing small improvements – every push to the repository triggers the whole pipeline. Other functional zones can be used to test compatibility with external systems. This requires a merge request to the repo &lt;em>master&lt;/em> branch.** As for production deployment, it can only be launched after all changes are accepted and merged.&lt;/p>
&lt;p>To summarize, here are the key elements of our downtime-free deployment:&lt;/p>
&lt;ul>
&lt;li>Roll out updates one data center at a time&lt;/li>
&lt;li>Rotate masters in replica sets&lt;/li>
&lt;li>Configure the load balancer to direct traffic to the active data center&lt;/li>
&lt;/ul>
&lt;p>It is important to maintain version and schema compatibility during updates. If there is an error at any stage, the update stops.&lt;/p>
&lt;p>Here is what the update process looks like:&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
Jenkins->>Data center 2: become master
Data center 2-->>Jenkins: OK
Jenkins->>Data center 2: nginx: switch traffic
Data center 2-->>Jenkins: OK
Jenkins->>Data center 1: update application version
activate Data center 1
NOTE right of Data center 1: new version &lt;br/>installation:&lt;br/>- port&lt;br/>availability check&lt;br/>- logrotate&lt;br/>- package installation&lt;br/>- orchestrator&lt;br/>configuration&lt;br/>- cluster build&lt;br/>- bootstrap&lt;br/>- failover&lt;br/>configuration
Data center 1-->>Jenkins: OK
deactivate Data center 1
Jenkins->>Data center 1: become master
Data center 1-->>Jenkins: OK
Jenkins->>Data center 1: nginx: switch traffic
Data center 1-->>Jenkins: OK
Jenkins->>Data center 2: update application version
activate Data center 2
NOTE right of Data center 2: new version&lt;br/>installation
Data center 2-->>Jenkins: OK
deactivate Data center 2
Jenkins->>Data center 1: become master
Data center 1-->>Jenkins: OK
Jenkins->>Data center 1: nginx: switch traffic
Data center 1-->>Jenkins: OK
&lt;/pre>
&lt;p>Currently, all updates require server restart. To find the right moment to continue our deployment, we have a special playbook that monitors instance states. Tarantool Cartridge has a state machine, and the state we are waiting for is called &lt;em>RolesConfigured&lt;/em>. It signifies that the instance is fully configured and ready to accept requests. If the application is deployed for the first time, the desired state would be &lt;em>Unconfigured&lt;/em>.&lt;/p>
&lt;p>The diagram above illustrates the general idea of downtime-free deployment, which can be easily scaled up to more data centers. You can update all the standby branches at once right after master rotation – that is, along with Data center 1 – or update them one by one, depending on your project requirements.&lt;/p>
&lt;p>Of course, we could not but make our work open source. You can find it in my ansible-cartridge fork on GitHub (&lt;a href="https://github.com/opomuc/ansible-cartridge" target="_blank" rel="noopener noreferrer">opomuc/ansible-cartridge&lt;/a>). Most of it has already been transferred to the master branch of the main repo.&lt;/p>
&lt;p>&lt;a href="https://github.com/opomuc/ansible-cartridge/tree/master/examples/deploy-by-dc" target="_blank" rel="noopener noreferrer">Here is our deployment example&lt;/a>. For it to work correctly, configure &lt;code>supervisord&lt;/code> on the server for the user &lt;code>tarantool&lt;/code>. See &lt;a href="https://github.com/opomuc/ansible-cartridge/blob/master/examples/deploy-with-targz/Vagrantfile#L18" target="_blank" rel="noopener noreferrer">this page&lt;/a> for the configuration commands. The application archive also has to contain the &lt;code>tarantool&lt;/code> binary.&lt;/p>
&lt;p>To launch branch-wise deployment, run the following commands:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Install application (for initial deployment)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml playbook.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.0.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'app_version=1.0.0' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags supervisor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Update version to 1.2.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Transfer master to dc2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml master.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.2.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit dc2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Update the main data center -- dc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml playbook.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.2.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'app_version=1.2.0' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags supervisor \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit dc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Transfer master to dc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml master.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.2.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit dc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Update the standby data center -- dc2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml playbook.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.2.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'app_version=1.2.0' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags supervisor \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit dc2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Make sure that the masters are in dc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ansible-playbook -i hosts.yml master.yml \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -b --become-user tarantool \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'base_dir=/data/tarantool' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --extra-vars 'cartridge_package_path=./getting-started-app-1.2.0-0.tar.gz' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit dc1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;code>base_dir&lt;/code> option specifies the path to your project’s home directory. After the deployment, the following subdirectories will be created:&lt;/p>
&lt;ul>
&lt;li>&lt;code>&lt;base_dir>/run&lt;/code> – for console sockets and pid files&lt;/li>
&lt;li>&lt;code>&lt;base_dir>/data&lt;/code> – for .snap and .xlog files, as well as Tarantool Cartridge configuration&lt;/li>
&lt;li>&lt;code>&lt;base_dir>/conf&lt;/code> – for application configuration and settings associated with specific instances&lt;/li>
&lt;li>&lt;code>&lt;base_dir>/releases&lt;/code> – for versioning and source code&lt;/li>
&lt;li>&lt;code>&lt;base_dir>/instances&lt;/code> – for links to the current version of every application instance&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>cartridge_package_path&lt;/code> option speaks for itself, but there is a trick:&lt;/p>
&lt;ul>
&lt;li>If the path starts with &lt;code>http://&lt;/code> or &lt;code>https://&lt;/code>, the artifact is pre-downloaded from the network (for example, from Artifactory).&lt;/li>
&lt;li>In other cases, the file search is performed locally.&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>app_version&lt;/code> option is used for versioning in the &lt;code>&lt;base_dir>/releases&lt;/code> folder. Its default value is &lt;code>latest&lt;/code>.&lt;/p>
&lt;p>The &lt;code>supervisor&lt;/code> tag means that &lt;code>supervisord&lt;/code> is the orchestrator.&lt;/p>
&lt;p>There are many ways to build a deployment, but the most reliable is good old &lt;code>Makefile&lt;/code>. The &lt;code>make &lt;deployment>&lt;/code> command works well for any CI/CD pipeline.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>That’s it! We made a Jenkins pipeline, got rid of mediators, and changes are now delivered at a crazy speed. The number of our users is growing. As many as 500 instances are running in our production environment, all of them deployed with our solution. Still, there is room for growth.&lt;/p>
&lt;p>Branch-wise deployment may not be perfect, but it provides firm support for further development of DevOps. Use our implementation with confidence to quickly deliver your system to production without worrying about pushing frequent changes.&lt;/p>
&lt;p>This was also a valuable lesson for us. You can’t take a monolith and hope that it will be a perfect fit in any situation. You need to divide playbooks into smaller parts, create separate roles for every installation stage, and make your inventory flexible. Some day all our work will be merged into the master branch – which will make everything even better.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;ul>
&lt;li>Step-by-step ansible-cartridge tutorial
&lt;ul>
&lt;li>&lt;a href="https://habr.com/ru/company/mailru/blog/478710/" target="_blank" rel="noopener noreferrer">Part 1&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://habr.com/ru/company/mailru/blog/484192/" target="_blank" rel="noopener noreferrer">Part 2&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Read more about Tarantool Cartridge &lt;a href="https://habr.com/ru/company/mailru/blog/465503/" target="_blank" rel="noopener noreferrer">here&lt;/a>&lt;/li>
&lt;li>Kubernetes deployment
&lt;ul>
&lt;li>&lt;a href="https://habr.com/ru/company/mailru/blog/533308/" target="_blank" rel="noopener noreferrer">A guide to using Tarantool Cartridge in Kubernetes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=8NvE6uooMQY&amp;ab_channel=Tarantool" target="_blank" rel="noopener noreferrer">Webinar: Deploying your Tarantool Cartridge application in an MCS Kubernetes cluster&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://habr.com/ru/company/mailru/blog/534826/" target="_blank" rel="noopener noreferrer">Tarantool monitoring: Logs, metrics, and their processing&lt;/a>&lt;/li>
&lt;li>Get help in our &lt;a href="https://t.me/tarantoolru?utm_source=habr&amp;utm_medium=articles&amp;utm_campaign=2020" target="_blank" rel="noopener noreferrer">Telegram chat&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Roman Proskin</author><category>tarantool</category><category>ansible</category><category>ops</category><category>tarantool cartridge</category><media:thumbnail url="https://percona.community/blog/2021/08/human-cover_hu_1ee1923249b2fb13.jpg"/><media:content url="https://percona.community/blog/2021/08/human-cover_hu_d154b65806482905.jpg" medium="image"/></item><item><title>Lets be inSync!</title><link>https://percona.community/blog/2021/07/22/lets-be-insync/</link><guid>https://percona.community/blog/2021/07/22/lets-be-insync/</guid><pubDate>Thu, 22 Jul 2021 00:00:00 UTC</pubDate><description>Percona Toolkit + pt-table-checksum + pt-table-sync = Faster Replica Recovery Asynchronous replication with MySQL is a tried and true technology. Add the use of GTID’s and you have a very stable solution.</description><content:encoded>&lt;h2 id="percona-toolkit--pt-table-checksum--pt-table-sync--faster-replica-recovery">Percona Toolkit + pt-table-checksum + pt-table-sync = Faster Replica Recovery&lt;/h2>
&lt;p>Asynchronous replication with MySQL is a tried and true technology. Add the use of GTID’s and you have a very
stable solution.&lt;/p>
&lt;p>The fundamental issue with async replication is that writes sent to the Replica are not guaranteed to be written. I have only seen a handful of times when writes did not get applied to the replica. Most of the time this happens is due to network packet drops or a replica crashes before new data is committed.&lt;/p>
&lt;p>I can remember long nights of restoring backups of the primary to the replica’s. Not a painful process but time consuming.&lt;/p>
&lt;p>Please take a few moments to review the full &lt;a href="https://www.percona.com/software/database-tools/percona-toolkit" target="_blank" rel="noopener noreferrer">documentation&lt;/a> of both tools before trying this example on live data: &lt;strong>pt-table-checksum, pt-table-sync&lt;/strong>.&lt;/p>
&lt;p>With pt-table-check and pt-table-sync provided by Percona Toolkit we can recover a replica without needed to do a restore. Keep in mind this approach might not work for all situations. We will go over one example below. We will also use dbdeployer to help us setup a testing sandbox.&lt;/p>
&lt;p>Let’s start off by setting up a VM to play with. For this I will be using Virtualbox and Ubuntu 20.04LTS.&lt;/p>
&lt;h3 id="prepare-ubuntu-2004lts">Prepare Ubuntu 20.04LTS&lt;/h3>
&lt;ol>
&lt;li>&lt;code>sudo apt install gnupg2 curl libaio-dev libncurses-dev mysql-client-core-8.0&lt;/code>&lt;/li>
&lt;li>&lt;code>wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb&lt;/code>&lt;/li>
&lt;li>&lt;code>sudo dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb&lt;/code>&lt;/li>
&lt;li>&lt;code>sudo percona-release enable tools release&lt;/code>&lt;/li>
&lt;li>&lt;code>sudo apt update&lt;/code>&lt;/li>
&lt;li>&lt;code>sudo apt install percona-toolkit sysbench&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="install-dbdeployer">Install dbdeployer&lt;/h3>
&lt;ol>
&lt;li>&lt;code>mkdir $HOME/bin ; cd $HOME/bin ; source $HOME/.profile&lt;/code>&lt;/li>
&lt;li>&lt;code>curl -s https://raw.githubusercontent.com/datacharmer/dbdeployer/master/scripts/dbdeployer-install.sh | bash&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="768" height="128" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-2_hu_482747af4cfdc5b.webp 768w, https://percona.community/blog/2021/07/lbis-2_hu_d356c06f5d464eec.webp 480w, https://percona.community/blog/2021/07/lbis-2_hu_482747af4cfdc5b.webp 768w" src="https://percona.community/blog/2021/07/lbis-2_hu_482747af4cfdc5b.webp" alt="lbis-2" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>&lt;code>ln -s dbdeployer-1.60.0.linux dbdeployer&lt;/code> (symlink for less typing)&lt;/li>
&lt;li>&lt;code>dbdeployer init&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="768" height="406" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-3_hu_fd495c66e6fbf2f0.webp 768w, https://percona.community/blog/2021/07/lbis-3_hu_4b4b20a1a44f8a25.webp 480w, https://percona.community/blog/2021/07/lbis-3_hu_fd495c66e6fbf2f0.webp 768w" src="https://percona.community/blog/2021/07/lbis-3_hu_fd495c66e6fbf2f0.webp" alt="lbis-3" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="5">
&lt;li>Download Percona Server: &lt;code>wget https://downloads.percona.com/downloads/Percona-Server-LATEST/Percona-Server-8.0.25-15/binary/tarball/Percona-Server-8.0.25-15-Linux.x86_64.glibc2.17-minimal.tar.gz&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="768" height="121" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-4_hu_cc85fd73e9d4316f.webp 768w, https://percona.community/blog/2021/07/lbis-4_hu_b2e24dc646bc134d.webp 480w, https://percona.community/blog/2021/07/lbis-4_hu_cc85fd73e9d4316f.webp 768w" src="https://percona.community/blog/2021/07/lbis-4_hu_cc85fd73e9d4316f.webp" alt="lbis-4" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Prepare Percona Server: &lt;code>dbdeployer --prefix=ps unpack Percona-Server-8.0.23-14-Linux.x86_64.glibc2.17-minimal.tar.gz&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="768" height="46" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-6_hu_2cba875450c01b9c.webp 768w, https://percona.community/blog/2021/07/lbis-6_hu_f26c7fec91047f6d.webp 480w, https://percona.community/blog/2021/07/lbis-6_hu_2cba875450c01b9c.webp 768w" src="https://percona.community/blog/2021/07/lbis-6_hu_2cba875450c01b9c.webp" alt="lbis-6" loading="lazy" />&lt;/figure>&lt;/p>
&lt;ol start="7">
&lt;li>Deploy your cluster:&lt;/li>
&lt;/ol>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> dbdeployer deploy replication ps8.0.23 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --gtid \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --custom-role-name=R_POWERFUL \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --custom-role-privileges='ALL PRIVILEGES' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --custom-role-target='*.*' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --custom-role-extra='WITH GRANT OPTION' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --default-role=R_POWERFUL \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --bind-address=0.0.0.0 \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --remote-access='%' \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --native-auth-plugin \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --db-user=sbtest \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --db-password=sbtest!&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lets verify our Cluster: &lt;code>dbdeployer sandboxes --full-info&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="768" height="75" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-7_hu_bdf37ac8387710fc.webp 768w, https://percona.community/blog/2021/07/lbis-7_hu_65fd27fce56b1414.webp 480w, https://percona.community/blog/2021/07/lbis-7_hu_bdf37ac8387710fc.webp 768w" src="https://percona.community/blog/2021/07/lbis-7_hu_bdf37ac8387710fc.webp" alt="lbis-7" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Change directories into you cluster directory: &lt;code>_$HOME/sandboxes/rsandboxps8.0.23_&lt;/code> and run the &lt;code>./check_slaves&lt;/code> script.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="669" height="476" sizes="(max-width: 669px) 100vw, 669px" srcset="https://percona.community/blog/2021/07/lbis-9_hu_f148d54fab8e61c1.webp 480w, https://percona.community/blog/2021/07/lbis-9_hu_f0a3ca7cf3d8eef.webp 669w" src="https://percona.community/blog/2021/07/lbis-9_hu_f0a3ca7cf3d8eef.webp" alt="lbis-9" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This will display information about your new cluster. Take time to make yourself familiar with the scripts in this directory.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> we will stay in the &lt;code>_$HOME/sandboxes/rsandboxps8.0.23_&lt;/code> for remainder of this post. * Please note that the location of your cluster might be different. *&lt;/p>
&lt;h3 id="preparing-data-for-testing">Preparing Data for testing&lt;/h3>
&lt;p>Let’s move on and add some data to play with. While in your sandboxes/cluster directory run this command:&lt;/p>
&lt;p>Connect you to the master: &lt;code>mysql --socket=/tmp/mysql_sandbox21325.sock --port=21325 -u sbtest -p&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">create database synctest;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use synctest;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">create table names (id int not null auto_increment primary key, fname varchar(50), lname varchar(50));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Moe','Howard');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Larry','Howard');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Curly','Howard');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Shemp','Howard');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Joe','Howard');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('James','Bond');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Doctor','No');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Gold','Finger');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Money','Penny');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Number','One');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values ('Number','Two');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into names (fname,lname) values (‘Micky','Mouse');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Make sure you do a quick &lt;code>select * from percona.synctest&lt;/code>;&lt;/p>
&lt;p>You should see 12 rows of data. If you don’t double check your inserts.&lt;/p>
&lt;p>Let’s connect to mysql and create a percona database and add the dsns table.
We will need this database and table to hold our checksums and DSNS data.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">create database percona;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use percona;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CREATE TABLE `dsns` (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `id` int(11) NOT NULL AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `parent_id` int(11) DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `dsn` varchar(255) NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PRIMARY KEY (`id`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into dsns (id,parent_id,dsn) values (1,1,"h=percona-lab,u=sbtest,p=sbtest!,P=21325");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">insert into dsns (id,parent_id,dsn) values (2,2,"h=percona-lab,u=sbtest,p=sbtest!,P=21326");&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>Remember to populate this data based on your cluster&lt;/strong>&lt;/p>
&lt;p>Quit out of your master sandbox.&lt;/p>
&lt;p>Now we are ready to move on to the pt-table-checksum tool.&lt;/p>
&lt;p>&lt;code>pt-table-checksum --user=sbtest --socket=/tmp/mysql_sandbox21324.sock --port=21234 --ask-pass --no-check-binlog-format&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="768" height="64" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-15_hu_2e1e242dbc778b56.webp 768w, https://percona.community/blog/2021/07/lbis-15_hu_11eff0272117342.webp 480w, https://percona.community/blog/2021/07/lbis-15_hu_2e1e242dbc778b56.webp 768w" src="https://percona.community/blog/2021/07/lbis-15_hu_2e1e242dbc778b56.webp" alt="lbis-15" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Notice we had errors: (I cropped out the rest of the output since it does not show a good run of pt-table-checksum.)
pt-table-checksum could not find the slaves. Lets run the command a second time, but this time lets tell it the the –recursion-method:&lt;/p>
&lt;p>&lt;code>pt-table-checksum --user=sbtest --socket=/tmp/mysql_sandbox21324.sock --port=21234 --ask-pass --no-check-binlog-format --recursion-method=dsn=D=percona,t=dsns&lt;/code>&lt;/p>
&lt;p>&lt;strong>Success!!!&lt;/strong> This time pt-table-checksum was able to find the replicas.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="768" height="389" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-16_hu_8e9d0262bf45d619.webp 768w, https://percona.community/blog/2021/07/lbis-16_hu_f49f6c901702b8dc.webp 480w, https://percona.community/blog/2021/07/lbis-16_hu_8e9d0262bf45d619.webp 768w" src="https://percona.community/blog/2021/07/lbis-16_hu_8e9d0262bf45d619.webp" alt="lbis-16" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Note: there a couple mysql tables that are different between the master and replicas. This is normal.&lt;/strong>&lt;/p>
&lt;h2 id="now-lets-remove-data-from-both-replicas">Now lets remove data from both replicas.&lt;/h2>
&lt;p>Connect to the 1st replica:
&lt;code>mysql --socket=/tmp/mysql_sandbox21325.sock --port=21325 -u sbtest -p&lt;/code>&lt;/p>
&lt;p>Change into the synctest database. Do a select on the synctest.names table and you should see 12 rows of data. Remove one row of data.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">delete from names where id = 7;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>quit out of slave1.&lt;/p>
&lt;p>Connect to the 2nd replica.
&lt;code>mysql --socket=/tmp/mysql_sandbox21326.sock --port=21326 -u sbtest -p&lt;/code>&lt;/p>
&lt;p>Change into the synctest database. Do a select on the names table and you should see 12 rows of data. Remove one row of data.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">delete from names where id = 8;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>quit out of slave2.&lt;/p>
&lt;p>Now we know that our cluster is out of sync, but let’s use the tool to verify. This time on checksum we will ignore mysql and sys databases.&lt;/p>
&lt;p>&lt;code>pt-table-checksum --user=sbtest --socket=/tmp/mysql_sandbox21324.sock --port=21234 --ask-pass --no-check-binlog-format —recursion-method=dsn=D=percona,t=dsns -- ignore-databases=mysql,sys&lt;/code>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="768" height="74" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-10_hu_bdace975f341d3d9.webp 768w, https://percona.community/blog/2021/07/lbis-10_hu_2b0f7a8b5800de5e.webp 480w, https://percona.community/blog/2021/07/lbis-10_hu_bdace975f341d3d9.webp 768w" src="https://percona.community/blog/2021/07/lbis-10_hu_bdace975f341d3d9.webp" alt="lbis-10" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Note: that pt-table-checksum is shows a DIFFS of 1 and DIFF_ROWS of 1. This reflects that we have 1 row of data missing from one our both of the slaves.&lt;/strong>&lt;/p>
&lt;p>Go back to slave2 and remove another row of data. Run Checksum again.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">delete from names where id = 9;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img width="768" height="74" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-11_hu_7bbd711139c9afbd.webp 768w, https://percona.community/blog/2021/07/lbis-11_hu_8f6d12889be8a87f.webp 480w, https://percona.community/blog/2021/07/lbis-11_hu_7bbd711139c9afbd.webp 768w" src="https://percona.community/blog/2021/07/lbis-11_hu_7bbd711139c9afbd.webp" alt="lbis-11" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This time we are seeing DIFF_ROWS of 2. This would now reflect that we have 2 rows missing on at least of one the slaves. Let’s fix this mess we created. Before we do that let’s look at the data on both slaves. As we can see they are not in-sync with the master.&lt;/p>
&lt;center> &lt;b>Slave1&lt;/b> &lt;/center>
&lt;p>
&lt;figure>&lt;img width="336" height="298" sizes="(max-width: 336px) 100vw, 336px" srcset="https://percona.community/blog/2021/07/lbis-13_hu_f10dd2086d1f9dcb.webp 336w" src="https://percona.community/blog/2021/07/lbis-13_hu_f10dd2086d1f9dcb.webp" alt="lbis-13" loading="lazy" />&lt;/figure>&lt;/p>
&lt;center> &lt;b>Slave2&lt;/b> &lt;/center>
&lt;p>
&lt;figure>&lt;img width="336" height="298" sizes="(max-width: 336px) 100vw, 336px" srcset="https://percona.community/blog/2021/07/lbis-12_hu_914483addb1e489d.webp 336w" src="https://percona.community/blog/2021/07/lbis-12_hu_914483addb1e489d.webp" alt="lbis-12" loading="lazy" />&lt;/figure>&lt;/p>
&lt;hr>
&lt;h2 id="now-lets-sync-the-slaves-to-the-master">Now let’s sync the slaves to the master.&lt;/h2>
&lt;p>Replication Safety is very important. Please take a moment to read the replication safety section of the pt-table-sync tool.&lt;/p>
&lt;p>&lt;code>pt-table-sync --execute h=percona-lab,P=21324,u=sbtest,p=sbtest! h=percona-lab,P=21325,u=sbtest,p=sbtest! h=percona-lab,P=21326,u=sbtest,p=sbtest! --no-check-slave --ignore-databases=mysql,sys&lt;/code>&lt;/p>
&lt;p>This will run in a couple seconds. When done lets checksum the cluster again.&lt;/p>
&lt;p>&lt;strong>Your cluster is now repaired.&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="768" height="72" sizes="(max-width: 768px) 100vw, 768px" srcset="https://percona.community/blog/2021/07/lbis-14_hu_19ae0e5a83ed4046.webp 768w, https://percona.community/blog/2021/07/lbis-14_hu_5e3542716c86f8eb.webp 480w, https://percona.community/blog/2021/07/lbis-14_hu_19ae0e5a83ed4046.webp 768w" src="https://percona.community/blog/2021/07/lbis-14_hu_19ae0e5a83ed4046.webp" alt="lbis-14" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is just an example of what the two tools can do, they may not meet your every need.&lt;/p>
&lt;p>If you look to use this to repair a production databases, &lt;strong>please make sure have have good backups on hand to fall back on&lt;/strong> if needed.&lt;/p>
&lt;h2 id="whats-next">Whats next?&lt;/h2>
&lt;p>I really only scratched the surface of these tools, dbdeployer, percona-toolkit.&lt;/p>
&lt;p>For more information on both tools please check out the the links below:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://www.dbdeployer.com/" target="_blank" rel="noopener noreferrer">dbdeployer&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/software/database-tools/percona-toolkit" target="_blank" rel="noopener noreferrer">Percona-Toolkit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/doc/percona-toolkit/LATEST/pt-table-checksum.html" target="_blank" rel="noopener noreferrer">pt-table-checksum&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/doc/percona-toolkit/LATEST/pt-table-sync.html" target="_blank" rel="noopener noreferrer">pt-table-sync&lt;/a>&lt;/li>
&lt;/ol></content:encoded><author>Wayne Leutwyler</author><category>Toolkit</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2021/07/lbis-1_hu_e04ff82b3f8b1772.jpg"/><media:content url="https://percona.community/blog/2021/07/lbis-1_hu_1c6e417900bfd5fb.jpg" medium="image"/></item><item><title>Create your own Exporter in Go!</title><link>https://percona.community/blog/2021/07/21/create-your-own-exporter-in-go/</link><guid>https://percona.community/blog/2021/07/21/create-your-own-exporter-in-go/</guid><pubDate>Wed, 21 Jul 2021 00:00:00 UTC</pubDate><description>Overview Hi, it’s too hot summer in Korea. Today I want to talk about an interesting and exciting topic. Try to making your own exporter in Go language.</description><content:encoded>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Hi, it’s too hot summer in Korea. Today I want to talk about an interesting and exciting topic. &lt;strong>Try to making your own exporter in Go language&lt;/strong>.&lt;/p>
&lt;p>If you register a specific query, it is a simple program that shows the result of this query as an exporter result metrics. Some of you may still be unfamiliar with what Expoter is.&lt;/p>
&lt;p>I will explain about Exporter step by step in today’s post.&lt;/p>
&lt;h2 id="exporter">Exporter?&lt;/h2>
&lt;p>You can think of an &lt;strong>Exporter as an HTTP server for pulling data from a time series database&lt;/strong> like Prometheus. Prometheus periodically calls the specific URL of the exporter and saves the result of metrics as a time series.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="934" height="442" sizes="(max-width: 934px) 100vw, 934px" srcset="https://percona.community/blog/2021/07/prometheus-exporter_hu_de0c79262968abbc.webp 768w, https://percona.community/blog/2021/07/prometheus-exporter_hu_10bea0f097eaffa2.webp 480w, https://percona.community/blog/2021/07/prometheus-exporter_hu_93d7e2391429367a.webp 934w" src="https://percona.community/blog/2021/07/prometheus-exporter_hu_93d7e2391429367a.webp" alt="prometheus exporter" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>There are many exporters exist in the everywhere.&lt;/p>
&lt;p>Typically, there is &lt;a href="https://github.com/prometheus/mysqld_exporter" target="_blank" rel="noopener noreferrer">mysqld_expoter&lt;/a>, which is Prometheus’s Offcial projects, and &lt;a href="https://github.com/percona/mysqld_exporter" target="_blank" rel="noopener noreferrer">mysqld_expoter&lt;/a>, which they fork and distribute additionally in Percona. Besides these, not only node_expoter for monitoring Linux nodes, but also memcached_expoter etc..&lt;/p>
&lt;p>For reference, you can see various exporters from &lt;a href="https://exporterhub.io" target="_blank" rel="noopener noreferrer">exporterhub&lt;/a>.&lt;/p>
&lt;p>What I am going to present my Blog that is the process of adding my own new exporter among these various exporters. Let’s go!&lt;/p>
&lt;h2 id="creating-a-go-project">Creating a Go project&lt;/h2>
&lt;p>Exporter can be implemented in various languages, but today I will implement it with Go.&lt;/p>
&lt;p>Personally, I think Go is very convenient in terms of distribution and compatibility. I will omit the go installation and environment configuration here.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> ~/go/src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir -p query-exporter-simple
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> query-exporter-simple
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ go mod init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: creating new go.mod: module query-exporter-simple
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls -al
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">3&lt;/span> chan staff &lt;span class="m">96&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">12&lt;/span> 13:33 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">12&lt;/span> chan staff &lt;span class="m">384&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">12&lt;/span> 13:33 ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> chan staff &lt;span class="m">38&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">12&lt;/span> 13:33 go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cat go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">module query-exporter-simple
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go 1.16&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Although it is an fundamental project, now everything is ready to make your own exporter. From now on, package management is managed with &lt;code>go mod&lt;/code>.&lt;/p>
&lt;h2 id="try-empty-exporter">Try Empty Exporter&lt;/h2>
&lt;p>Now, let’s start making the Exporter in earnest.&lt;/p>
&lt;p>First, as a taster, let’s try to make an empty Exporter that has no function.. it simply outputs the exporter version only.&lt;/p>
&lt;p>This is to read OS parameters using flags. The “bind” is server HTTP binding information when Exporter is started.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"flag"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get OS parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">bind&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"0.0.0.0:9104"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Register Collector to collect and run Exporter with HTTP server. Collector is the concept of a thread that collects information, and it implements the Collector interface of Prometheus.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"flag"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"net/http"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus/promhttp"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/common/version"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span> &lt;span class="s">"github.com/sirupsen/logrus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get OS parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">bind&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"0.0.0.0:9104"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCollector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist http handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"/metrics"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandlerFor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Gatherers&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DefaultGatherer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HandlerOpts&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// start server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Starting http server - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bind&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to start http server: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Since the packages used by source code do not exist in the project yet, numerous errors will be occurred.&lt;/p>
&lt;p>So, as below, get related packages through &lt;code>go mod vendor&lt;/code>. Related packages are placed under the vendor directory.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ go mod vendor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: finding module &lt;span class="k">for&lt;/span> package github.com/prometheus/common/version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: finding module &lt;span class="k">for&lt;/span> package github.com/prometheus/client_golang/prometheus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: finding module &lt;span class="k">for&lt;/span> package github.com/sirupsen/logrus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: finding module &lt;span class="k">for&lt;/span> package github.com/prometheus/client_golang/prometheus/promhttp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: found github.com/prometheus/client_golang/prometheus in github.com/prometheus/client_golang v1.11.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: found github.com/prometheus/client_golang/prometheus/promhttp in github.com/prometheus/client_golang v1.11.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: found github.com/prometheus/common/version in github.com/prometheus/common v0.29.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: found github.com/sirupsen/logrus in github.com/sirupsen/logrus v1.8.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls -al
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">112&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">6&lt;/span> chan staff &lt;span class="m">192&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">13&lt;/span> 10:26 .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">12&lt;/span> chan staff &lt;span class="m">384&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">12&lt;/span> 13:33 ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> chan staff &lt;span class="m">169&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">13&lt;/span> 10:26 go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> chan staff &lt;span class="m">45722&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">13&lt;/span> 10:26 go.sum
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- &lt;span class="m">1&lt;/span> chan staff &lt;span class="m">1163&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">13&lt;/span> 10:34 main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drwxr-xr-x &lt;span class="m">6&lt;/span> chan staff &lt;span class="m">192&lt;/span> &lt;span class="m">7&lt;/span> &lt;span class="m">13&lt;/span> 10:26 vendor&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you start the Exporter server, the server will be run on port 9104 (the port specified by default in flag).&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ go run .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Regist version collector - query_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> HTTP handler path - /metrics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Starting http server - 0.0.0.0:9104&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you want to change the port, give the bind OS parameter as below, then, the server will run with that port.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ go run . --bind&lt;span class="o">=&lt;/span>0.0.0.0:9105
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Regist version collector - query_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> HTTP handler path - /metrics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Starting http server - 0.0.0.0:9105&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Even though it is an empty Exporter.. You can see that a lot of information is extracted through the Exporter. (Most of the information is about go itself..)&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ curl 127.0.0.1:9104/metrics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE go_gc_duration_seconds summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go_gc_duration_seconds&lt;span class="o">{&lt;/span>&lt;span class="nv">quantile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"0"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go_gc_duration_seconds&lt;span class="o">{&lt;/span>&lt;span class="nv">quantile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"0.25"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.. skip ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP go_threads Number of OS threads created.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE go_threads gauge&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go_threads &lt;span class="m">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP query_exporter_build_info A metric with a constant '1' value labeled by version, revision, branch, and goversion from which query_exporter was built.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE query_exporter_build_info gauge&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_build_info&lt;span class="o">{&lt;/span>&lt;span class="nv">branch&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>,goversion&lt;span class="o">=&lt;/span>&lt;span class="s2">"go1.16.5"&lt;/span>,revision&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>,version&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At the very bottom, there is the query_exporter_build_info metric, which is the information collected by the Collector that we added in the previous section. This is the moment we created the new Exporter collecting version information!&lt;/p>
&lt;h2 id="creating-an-exporter-in-earnest">Creating an Exporter in earnest&lt;/h2>
&lt;p>I made an empty Exporter that specifies only the exporter version. Is that easy, right? 🙂&lt;/p>
&lt;p>From now on, I’m going to implement a Collector that collects the information we really need from database and sends the result to the HTTP GET method.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1238" height="478" sizes="(max-width: 1238px) 100vw, 1238px" srcset="https://percona.community/blog/2021/07/query-exporter_hu_cadc4c447211fd5f.webp 768w, https://percona.community/blog/2021/07/query-exporter_hu_9392ffeffb092fbe.webp 480w, https://percona.community/blog/2021/07/query-exporter_hu_c6a44bb3b7cf37be.webp 1238w" src="https://percona.community/blog/2021/07/query-exporter_hu_c6a44bb3b7cf37be.webp" alt="query exporter" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="1-configuration-format-yaml">1. Configuration format (YAML)&lt;/h3>
&lt;p>As I said before, I want to make something that passes the result of the registered query to the Exporter result metric. To do this, you need to know information about the target instance as well as the query to be executed.&lt;/p>
&lt;p>Let’s set it up in the below format. MySQL connection information and the query to be executed. It will show two pieces of information as a result: &lt;strong>“Connections per host”&lt;/strong> and &lt;strong>“Connections per user”&lt;/strong>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">dsn&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test:test123@tcp(127.0.0.1:3306)/information_schema&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">process_count_by_host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"select user,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> substring_index(host, ':', 1) host,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> count(*) sessions
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> from information_schema.processlist
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> group by 1,2 "&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gauge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"process count by host"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"user"&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">"host"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sessions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">process_count_by_user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"select user, count(*) sessions
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> from information_schema.processlist
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> group by 1 "&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gauge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"process count by user"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"user"&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sessions&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I tried defining the above yaml as Go structure.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DSN&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Metrics&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Query&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Type&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Description&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Labels&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Value&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metricDesc&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here, metricDesc &lt;code>*prometheus.Desc&lt;/code> can be understood as a specification used in Prometheus metrics. It also specifies any label and metric types such as Counter/Gauge.&lt;/p>
&lt;p>Read the YAML file as below, and finally load the setting information into the structure defined below.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="nx">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">ioutil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"config.yml"&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to read config file: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Load yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">yaml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmarshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to load config: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this way, we can now put the necessary information in the Config structure and use it to implement the desired implementation.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"flag"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"io/ioutil"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"net/http"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"os"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/ghodss/yaml"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus/promhttp"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/common/version"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span> &lt;span class="s">"github.com/sirupsen/logrus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="nx">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">configFile&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bind&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get OS parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">configFile&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"config"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"config.yml"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"configuration file"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"0.0.0.0:9104"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Load config &amp; yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">ioutil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">configFile&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to read config file: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Load yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">yaml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmarshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to load config: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Regist version collector - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCollector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist http handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"HTTP handler path - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"/metrics"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"/metrics"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandlerFor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Gatherers&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DefaultGatherer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HandlerOpts&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// start server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Starting http server - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bind&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to start http server: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Config config structure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DSN&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Metrics&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Query&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Type&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Description&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Labels&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Value&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metricDesc&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="2-implement-collector">2. Implement Collector&lt;/h3>
&lt;p>The highlight of today’s post is the implementing a Collector to collect the desired information from database.&lt;/p>
&lt;p>All the processes I implemented so far is to get the results as an HTTP result. Collector actually connect to the database and delivering the specified metric result based on the result of executing the specified query.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">QueryCollector&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Describe prometheus describe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Describe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Collect prometheus collect&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Collect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metric&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As I have mentioned earlier, Collector is kind of a thread concept that collects information, and is a structure that implements the Collector interface of Prometheus. In other words, this story means that if you want to create another Collector of your own, &lt;strong>you must implement two of the Describe and Collect defined by the prometheus.Collector interface&lt;/strong>.&lt;/p>
&lt;p>Register the Collector defined as below.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">(){&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">..&lt;/span> &lt;span class="nx">skip&lt;/span> &lt;span class="p">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Regist version collector - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCollector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">..&lt;/span> &lt;span class="nx">skip&lt;/span> &lt;span class="p">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The Version Collector added to the can exporter created earlier and the QueryCollector newly added this time are registered. When an http request comes in to “/metric”, the above two Collectors are finally executed by each thread.&lt;/p>
&lt;h4 id="2-1-create-the-describe-function">2-1. Create the Describe function&lt;/h4>
&lt;p>&lt;strong>This is the part that defines the specifications of each metric.&lt;/strong> Actually, it is not necessary to define the specification of the metric here, but it is useful if you consider the case of creating and operating multiple Collectors. This method is executed only once when a Collector is registered with prometheus.Register.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Describe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDesc&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">BuildFQName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"query_exporter"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">""&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Description&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Labels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">metricName&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">metric&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"metric description for \"%s\" registerd"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here, I have defined the specification of the metric with the information related to Query in the setting information read earlier.&lt;/p>
&lt;ul>
&lt;li>prometheus.BuildFQName: name of metric&lt;/li>
&lt;li>metric.Description: Description&lt;/li>
&lt;li>metric.Labels: Array of label names, label values should be mapped later in this order&lt;/li>
&lt;/ul>
&lt;p>If you look at the config.yml, each mapping will be as follows.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># metricName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">process_count_by_user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c">## metric.Description&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"process count by user"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c">## metric.Labels&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">"user"&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="2-2-create-the-collect-function">2-2. Create the Collect function&lt;/h4>
&lt;p>This is the part that connects to the DB, executes the registered SQL, and makes it a metric.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1486" height="384" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2021/07/metric-results_hu_bb216d2ff023c89.webp 768w, https://percona.community/blog/2021/07/metric-results_hu_f11dd5a5e67d5880.webp 480w, https://percona.community/blog/2021/07/metric-results_hu_378cee3f9b83a4c0.webp 1400w" src="https://percona.community/blog/2021/07/metric-results_hu_378cee3f9b83a4c0.webp" alt="metric results" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The execution results(rows) of each query are displayed as a metric name and values as shown in the figure above.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Collect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metric&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Connect to database&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">db&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"mysql"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DSN&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Connect to database failed: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Execute each queries in metrics&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Execute query&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rows&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to execute query: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get column info&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cols&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Columns&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to get column meta: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">des&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kd">interface&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([][]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">cols&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">des&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// fetch database&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Next&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Scan&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">des&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">res&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Metric labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">labelVals&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">label&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Labels&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">labelVals&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">labelVals&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">label&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Metric value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ParseFloat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Value&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Add metric&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ToLower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Type&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">"counter"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CounterValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">"gauge"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GaugeValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Fail to add metric for %s: %s is not valid type"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Type&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see from the labelVals value, you need to pass the label values in the order of Labels of the specification defined in Describe earlier. There are two metric types here: &lt;strong>counter&lt;/strong> and &lt;strong>gauge&lt;/strong>. Each type has the following meaning.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>COUNTER&lt;/strong>: A value that only increases. In prometheus, the indicator is displayed as a change calculation function such as rate/irate.&lt;/li>
&lt;li>&lt;strong>GAUGE&lt;/strong>: A type whose value can increase/decrease, such as like car gauge. In general, it is used to save the current metric value as it is, such as process count.&lt;/li>
&lt;/ul>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// COUNTER&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CounterValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// GAUGE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GaugeValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For the value to be displayed as a metric, the value item specified in the setting is retrieved from the query result.&lt;/p>
&lt;h2 id="queryexporter-source">QueryExporter Source&lt;/h2>
&lt;p>Here’s the everything that I’ve done so far:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">go&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"database/sql"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"flag"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"io/ioutil"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"net/http"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"os"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"strconv"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"strings"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/ghodss/yaml"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_&lt;/span> &lt;span class="s">"github.com/go-sql-driver/mysql"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/client_golang/prometheus/promhttp"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">"github.com/prometheus/common/version"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span> &lt;span class="s">"github.com/sirupsen/logrus"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="nx">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">collector&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">"query_exporter"&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">configFile&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bind&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get OS parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">configFile&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"config"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"config.yml"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"configuration file"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"0.0.0.0:9104"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"bind"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Load config &amp; yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// =====================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">ioutil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">configFile&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to read config file: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Load yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">yaml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmarshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to load config: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ========================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Regist version collector - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">collector&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCollector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">collector&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Regist http handler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"HTTP handler path - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">"/metrics"&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"/metrics"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandlerFor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Gatherers&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DefaultGatherer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">promhttp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HandlerOpts&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// start server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Starting http server - %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bind&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bind&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to start http server: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Config config structure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DSN&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Metrics&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Query&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Type&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Description&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Labels&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Value&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metricDesc&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// QueryCollector exporter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// =============================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">QueryCollector&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Describe prometheus describe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Describe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Desc&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDesc&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">BuildFQName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">collector&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">""&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Description&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Labels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">metricName&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">metric&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Infof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"metric description for \"%s\" registerd"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metricName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Collect prometheus collect&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">QueryCollector&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Collect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metric&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Connect to database&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">db&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"mysql"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DSN&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Connect to database failed: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Execute each queries in metrics&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Metrics&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Execute query&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rows&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to execute query: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Get column info&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cols&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Columns&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Failed to get column meta: %s"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">des&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kd">interface&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([][]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">cols&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">des&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// fetch database&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Next&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Scan&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">des&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">res&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">cols&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Metric labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">labelVals&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">label&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Labels&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">labelVals&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">labelVals&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">label&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Metric value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ParseFloat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Value&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Add metric&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ToLower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Type&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">"counter"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CounterValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">"gauge"&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ch&lt;/span> &lt;span class="o">&lt;-&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustNewConstMetric&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metricDesc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prometheus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GaugeValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">labelVals&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">"Fail to add metric for %s: %s is not valid type"&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">metric&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Type&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If the package does not exist, run &lt;code>go mod vendor&lt;/code> to download the necessary packages.&lt;/p>
&lt;p>Start the server and check the information collected by the actual exporter.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ go run .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Regist version collector - query_exporter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> metric description &lt;span class="k">for&lt;/span> &lt;span class="s2">"process_count_by_host"&lt;/span> registerd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> metric description &lt;span class="k">for&lt;/span> &lt;span class="s2">"process_count_by_user"&lt;/span> registerd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> HTTP handler path - /metrics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO&lt;span class="o">[&lt;/span>0000&lt;span class="o">]&lt;/span> Starting http server - 0.0.0.0:9104&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you run it with curl, you can see that the session count per user/host defined in the settings is displayed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">bash&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ curl 127.0.0.1:9104/metrics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE go_gc_duration_seconds summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go_gc_duration_seconds&lt;span class="o">{&lt;/span>&lt;span class="nv">quantile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"0"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go_gc_duration_seconds&lt;span class="o">{&lt;/span>&lt;span class="nv">quantile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"0.25"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.. skip ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP query_exporter_build_info A metric with a constant '1' value labeled by version, revision, branch, and goversion from which query_exporter was built.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE query_exporter_build_info gauge&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_build_info&lt;span class="o">{&lt;/span>&lt;span class="nv">branch&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>,goversion&lt;span class="o">=&lt;/span>&lt;span class="s2">"go1.16.5"&lt;/span>,revision&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>,version&lt;span class="o">=&lt;/span>&lt;span class="s2">""&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP query_exporter_process_count_by_host process count by host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE query_exporter_process_count_by_host gauge&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_process_count_by_host&lt;span class="o">{&lt;/span>&lt;span class="nv">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"localhost"&lt;/span>,user&lt;span class="o">=&lt;/span>&lt;span class="s2">"event_scheduler"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_process_count_by_host&lt;span class="o">{&lt;/span>&lt;span class="nv">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"localhost"&lt;/span>,user&lt;span class="o">=&lt;/span>&lt;span class="s2">"test"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HELP query_exporter_process_count_by_user process count by user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># TYPE query_exporter_process_count_by_user gauge&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_process_count_by_user&lt;span class="o">{&lt;/span>&lt;span class="nv">user&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"event_scheduler"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_exporter_process_count_by_user&lt;span class="o">{&lt;/span>&lt;span class="nv">user&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">"test"&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is the moment when your own Exporter is created at final!. 🙂&lt;/p>
&lt;h2 id="concluding">Concluding..&lt;/h2>
&lt;p>The post was very long. I put the source code in the body several times.. I feel like the amount of text is getting longer.&lt;/p>
&lt;p>Anyway, I’ve created my own unique Exporter! &lt;strong>I implemented a simple function to simply register a query and extract this result as a metric result&lt;/strong>, but I think you can add more interesting elements according to your own thoughts as needed.&lt;/p>
&lt;p>For reference, the source written above is organized in the following Git.&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://github.com/go-gywn/query-exporter-simple" target="_blank" rel="noopener noreferrer">https://github.com/go-gywn/query-exporter-simple&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>Sometimes, when I need to monitor hundreds and thousands of servers from one monitoring server, it is sometimes useful to manage the collection of metrics. As of yet, only support with MySQL, I personally create another Query Exporter project. I implemented more parallel processing and timeouts in the above project base.&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://github.com/go-gywn/query-exporter" target="_blank" rel="noopener noreferrer">https://github.com/go-gywn/query-exporter&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>It’s always been like that… If there is nothing, just create and it If there is, use it well!&lt;/p>
&lt;p>I hope to all you have a nice summer.&lt;/p></content:encoded><author>Dongchan Sung</author><category>Exporter</category><category>Go</category><category>Query</category><category>MySQL</category><category>Prometheus</category><category>Programming</category><media:thumbnail url="https://percona.community/blog/2021/07/query-exporter_hu_5c9539ca66ebe142.jpg"/><media:content url="https://percona.community/blog/2021/07/query-exporter_hu_e31fad24a19a09b2.jpg" medium="image"/></item><item><title>Exporters Roadmap</title><link>https://percona.community/blog/2021/06/11/exporters-roadmap/</link><guid>https://percona.community/blog/2021/06/11/exporters-roadmap/</guid><pubDate>Fri, 11 Jun 2021 00:00:00 UTC</pubDate><description>Exporters Roadmap Goals Prometheus exports as a part of PMM are a big and valuable component.</description><content:encoded>&lt;h2 id="exporters-roadmap">Exporters Roadmap&lt;/h2>
&lt;h3 id="goals">Goals&lt;/h3>
&lt;p>Prometheus exports as a part of PMM are a big and valuable component.&lt;/p>
&lt;p>According to the goal to involve open source contributors to contribute to PMM and Percona to contribute to open source. As the main focus, it was decided to start from the exporter.&lt;/p>
&lt;p>For now PMM use the next exporters:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://github.com/percona/node_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/node_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/mysqld_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/mysqld_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/mongodb_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/mongodb_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/postgres_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/postgres_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/Percona-Lab/clickhouse_exporter" target="_blank" rel="noopener noreferrer">https://github.com/Percona-Lab/clickhouse_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/proxysql_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/proxysql_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/rds_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/rds_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/azure_metrics_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/azure_metrics_exporter&lt;/a>&lt;/li>
&lt;/ol>
&lt;h3 id="groups">Groups&lt;/h3>
&lt;p>We can split them into three groups.&lt;/p>
&lt;p>&lt;strong>The first group&lt;/strong> is exporters that are created by Percona or Percona contribution in its fork is so big - that it cannot be pushed back upstream.&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://github.com/percona/mongodb_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/mongodb_exporter&lt;/a> - built by Percona from scratch.&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/proxysql_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/proxysql_exporter&lt;/a> - built by Percona from scratch.&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/rds_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/rds_exporter&lt;/a> - too far from upstream - Percona made a big contribution to fit it for PMM needs.&lt;/li>
&lt;/ol>
&lt;p>For those three exporters we are going to:&lt;/p>
&lt;ul>
&lt;li>encourage contribution from the community;&lt;/li>
&lt;li>create an easy setup dev environment to speed up development and testing;&lt;/li>
&lt;li>consider user’s issues and request - and try to solve this with “community priority” level;&lt;/li>
&lt;li>create regular releases with needed binaries for community consumption.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The second group&lt;/strong> is exporters that are not that far away from upstream and Percona would like to contribute back as much as possible.&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://github.com/percona/node_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/node_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/mysqld_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/mysqld_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/postgres_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/postgres_exporter&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>For this group, we will try to push all fixes made by Percona to Upstream and are going to take part in development and bug fixing as open-source contributors - trying to bring value for the community as well as for PMM.&lt;/p>
&lt;p>And &lt;strong>the third group&lt;/strong> are exporters that currently fit PMM needs and Percona did not contribute a lot in its forks.&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://github.com/Percona-Lab/clickhouse_exporter" target="_blank" rel="noopener noreferrer">https://github.com/Percona-Lab/clickhouse_exporter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/percona/azure_metrics_exporter" target="_blank" rel="noopener noreferrer">https://github.com/percona/azure_metrics_exporter&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>For those exporters, we are going to start using upstream and make changes if needed in other PMM components. Downstream repos would only be used as forks synced with upstream only for the PMM build support.&lt;/p>
&lt;h3 id="action-plan">Action plan&lt;/h3>
&lt;p>Here is some short term plan of tasks to implement a part of the plan above:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Remove fork of clickhouse_exporter and remove it as component from PMM - looks like the easiest task. The new version of the ClickHouse server exposes metrics in Prometheus format, so we can collect them without any exporters. (we can use build-in metrics exporter &lt;a href="https://clickhouse.tech/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-prometheus" target="_blank" rel="noopener noreferrer">https://clickhouse.tech/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-prometheus&lt;/a> starting from &lt;a href="https://clickhouse.tech/docs/en/whats-new/changelog/2020/#clickhouse-release-v20-1-2-4-2020-01-22" target="_blank" rel="noopener noreferrer">https://clickhouse.tech/docs/en/whats-new/changelog/2020/#clickhouse-release-v20-1-2-4-2020-01-22&lt;/a>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Discard our changes in azure_metrics_exporter and keep the fork in sync with upstream. We use common formulas in Grafana to visualize metrics from different exporters. Current azure_exporter has slightly different a few metric names - we can achieve the same by renaming using Prometheus recording rules &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/recording_rules" target="_blank" rel="noopener noreferrer">https://prometheus.io/docs/prometheus/latest/configuration/recording_rules&lt;/a> . This discard needs to keep the fork up to date with upstream.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Node exporter looks like the best candidate to contribute back to the community &lt;a href="https://github.com/prometheus/node_exporter/compare/master...percona:main" target="_blank" rel="noopener noreferrer">https://github.com/prometheus/node_exporter/compare/master...percona:main&lt;/a>. This exporter’s source code did not go far away - so we can leverage what we can accept from upstream and create minimal PR to upstream with features we only required.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>MySQL exporter can be the heaviest task to push back to upstream - we did a lot of change. So the tactic could be split difference into logical parts and try to push back it step by step &lt;a href="https://github.com/percona/mysqld_exporter/pull/61/files" target="_blank" rel="noopener noreferrer">https://github.com/percona/mysqld_exporter/pull/61/files&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>PostgreSQL exporter is also quite far from upstream plus it requires a few fundamental improvements like a handle DB connection, etc. For this exporter, we also need split difference on logical parts and contribute it with small PR back to upstream &lt;a href="https://github.com/percona/postgres_exporter/pull/28/files" target="_blank" rel="noopener noreferrer">https://github.com/percona/postgres_exporter/pull/28/files&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Maintain mongodb_exporter, add needed packaging, docker container and update helm chart.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Proxy exporter looks good for now, but we need to take into consideration that ProxySQL start exports metrics natively &lt;a href="https://proxysql.com/documentation/prometheus-exporter" target="_blank" rel="noopener noreferrer">https://proxysql.com/documentation/prometheus-exporter&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>And RDS exporter goes to be separated from upstream - now it contains a big part of code that serve mostly PMM needs.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>For all the above we would try to use the GitHub Project board &lt;a href="https://github.com/orgs/percona/projects/2" target="_blank" rel="noopener noreferrer">https://github.com/orgs/percona/projects/2&lt;/a> to track progress in different repositories for all mentioned tasks above.&lt;/p>
&lt;p>We would sync with the community during Engineering Monthly Meeting &lt;a href="https://percona.community/contribute/engineeringmeetings/" target="_blank" rel="noopener noreferrer">https://percona.community/contribute/engineeringmeetings/&lt;/a> as well as by participating in Upstream meetings.&lt;/p>
&lt;p>Come and join us on our journey in OpenSource! Contact us at &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> .&lt;/p></content:encoded><author>Andrii Skomorokhov</author><category>Exporter</category><category>Prometheus</category><category>PMM</category><category>Monitoring</category><media:thumbnail url="https://percona.community/blog/2021/06/pmm-exporters_hu_d93268051f105860.jpg"/><media:content url="https://percona.community/blog/2021/06/pmm-exporters_hu_5dd9a213d87f4edc.jpg" medium="image"/></item><item><title>How to Speed Up Re Sync of Dropped Percona Xtradb Cluster Node</title><link>https://percona.community/blog/2021/02/24/how-to-speed-up-re-sync-of-dropped-percona-xtradb-cluster-node/</link><guid>https://percona.community/blog/2021/02/24/how-to-speed-up-re-sync-of-dropped-percona-xtradb-cluster-node/</guid><pubDate>Wed, 24 Feb 2021 00:00:00 UTC</pubDate><description>The Problem HELP, HELP! My Percona XtraDB Cluster version: 5.7.31-31. Single Node is stuck in a joined state.</description><content:encoded>&lt;h2 id="the-problem">The Problem&lt;/h2>
&lt;p>HELP, HELP! My Percona XtraDB Cluster version: 5.7.31-31. Single Node is stuck in a joined state.&lt;/p>
&lt;p>I recently had the privilege to help a client with a fascinating issue.&lt;/p>
&lt;p>NODE-B dropped out of the 3 node PXC cluster. It looked to be DISK IO that caused NODE-B to fall far behind and eventually be removed from the cluster. A restart of NODE-B allowed it
to rejoin the cluster. NODE-B looked to have been down for about 4 hours. Once NODE-B was back as part of the cluster, it required a full SST.&lt;/p>
&lt;p>When NODE-B stayed in a joint state for more than 12 hours, the client gave me a call. They were concerned that there was another issue with this cluster.&lt;/p>
&lt;p>Before going forward, let’s make sure we know the CPU, RAM and Database Size.&lt;/p>
&lt;ul>
&lt;li>8 CPU&lt;/li>
&lt;li>32 GB RAM&lt;/li>
&lt;li>Database Size approx. 2.75TB&lt;/li>
&lt;/ul>
&lt;p>Let’s gather some base information.&lt;/p>
&lt;p>I pulled the below data once I understood what was going on.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SHOW STATUS LIKE ‘wsrep_last%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_applied | 9802457 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_committed | 10103670 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">+---------------------------+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------------+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_local_state_comment | Joined |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------------+--------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SHOW STATUS LIKE 'wsrep_cert_deps_distance';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_cert_deps_distance | 148.96 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------+---------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Pulled the below stats about one hour later.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">NODE-B
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_applied | 11901100 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_committed | 12801100 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">NODE-A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_applied | 32900981 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_committed | 32901100 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we can see above, NODE-B is processing write sets, but very slowly. The gcache files were being consumed very quickly, but being only 128MB in size would be slow going to get in sync. At this time, NODE-A and NODE-B seqno’s were separated by 20,100,000.&lt;/p>
&lt;p>Now we know NODE-B is working as it should. At this rate, it could be a day or more to catch up.&lt;/p>
&lt;h2 id="gathering-data-and-coming-up-with-a-solution">Gathering Data and Coming up with a solution&lt;/h2>
&lt;p>I did a quick review of the PXC settings and found:&lt;/p>
&lt;ol>
&lt;li>The wsrep_slave_threads = 2&lt;/li>
&lt;li>Many tables had no primary key. The mysql.log file was approx 500Mb in size. The gal-leria cache size was set at the default 128MB (Now I saw why NODE-B needed a fullSST)&lt;/li>
&lt;li>The client had set the wsrep_doner_node to use NODE-C. NODE-C had a higher laten-cy to NODE-B than NODE-B had to NODE-A. I would prefer to have PXC choose the donor. Not have it set up to use NODE-C.&lt;/li>
&lt;/ol>
&lt;p>A scheduled 500 million row data extract started right about the time NODE-B re-joined the cluster. Now we have a large data load taking place plus a full SST to NODE-B.&lt;/p>
&lt;p>Let’s now talk about how we helped to speed up NODE-B going from Joined to Synced.&lt;/p>
&lt;h2 id="recommendations">Recommendations&lt;/h2>
&lt;p>We upped the slave threads from 2 to 8. This is equal to the number of CPU’s on the system. Exceeding 8 threads could cause performance impact.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">mysql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-mysql" data-lang="mysql">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">global&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wsrep_slave_threads&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Changed pxc_stright_mode from permissive too disabled. This was done to stop all PXC warnings being written to mysqld.log.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">mysql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-mysql" data-lang="mysql">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">global&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pxc_strick_mode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">disabled&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Relaxed ACID compliance. I made these changes to help NODE-B get back into a sync status quicker. I don’t recommend relaxing ACID compliance. This change should only be made if the client fully understands the risk.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">mysql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-mysql" data-lang="mysql">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">global&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">innodb_flush_log_at_trx_commit&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kt">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">global&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sync_binlog&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We let these changes bake in for about 2 hours. The client did not want to stop the data extract just yet. They were very open to the idea and did not want to lose the work that had already been completed. This did not bother me because I know the NODE-B was working as it should be. We let these changes bake in for about two hours.&lt;/p>
&lt;h2 id="improvement">Improvement&lt;/h2>
&lt;p>NODE-B&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SHOW STATUS LIKE ‘wsrep_last%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_applied | 32902200 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| wsrep_last_committed | 40902100 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------+----------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SHOW STATUS LIKE 'wsrep_cert_deps_distance';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +--------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +--------------------------+---------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | wsrep_cert_deps_distance | 86.81 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +--------------------------+---------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +---------------------------+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +---------------------------+--------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | wsrep_local_state_comment | Joined |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +---------------------------+--------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now let’s look at our primary read/write NODE-A:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> SHOW STATUS LIKE ‘wsrep_last%';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | Variable_name | Value |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | wsrep_last_applied | 43900992 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +----------------------+----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | wsrep_last_committed | 43902200 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +----------------------+----------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we can now see, NODE-B is catching up much faster than before. The committed seqno is only 3,000,100 apart now, where the seqno had been this far apart 20,100,000.&lt;/p>
&lt;p>Clearly, we made some significant progress. The client still was concerned about only having 2 of the 3 nodes up. We had a couple of choices, one stops the data extract or be patient for a bit longer. Clients choose patience. After another 2.5 hours, NODE-B had caught up to its peers and switched to Synced.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>NODE-B was stuck in a joined state due to a very undersized gcache; the default size had nev- er been changed.&lt;/p>
&lt;ul>
&lt;li>Review your Percona XtraDB Cluster setting; if you have an extensive data set, the default gcache size won’t be enough. Not sure how to best size the cache? Look here:• Miguel Angel Nieto wrote a great blog post to help size the galera cache.&lt;/li>
&lt;li>Give the cluster a regular health check. This is critical as your database grows.&lt;/li>
&lt;li>Make sure all your tables have Primary Keys. Without the use of primary keys on all tables;your performance will suffer.&lt;/li>
&lt;li>Make sure you are getting all the performance you can.• Useful link: Tips for MySQL 5.7 Database Tuning and Performance&lt;/li>
&lt;li>As you can see, adjusting the number of threads applying transactions can make a big dif-ference. Just don’t go overboard.&lt;/li>
&lt;li>If possible large data loads should be done in off-hours.&lt;/li>
&lt;/ul></content:encoded><author>Wayne Leutwyler</author><media:thumbnail url="https://percona.community/blog/2021/02/Blog-Community-Pic_hu_1e650b39cb6c10b6.jpg"/><media:content url="https://percona.community/blog/2021/02/Blog-Community-Pic_hu_7d2b6a1704ddd161.jpg" medium="image"/></item><item><title>fork, exec, wait and exit</title><link>https://percona.community/blog/2021/01/04/fork-exec-wait-and-exit/</link><guid>https://percona.community/blog/2021/01/04/fork-exec-wait-and-exit/</guid><pubDate>Mon, 04 Jan 2021 16:06:48 UTC</pubDate><description>This is the english version of a 2007 article. In de.comp.os.unix.linux.misc somebody asked:</description><content:encoded>&lt;p>This is the &lt;a href="https://isotopp.github.io/2007/01/07/fork-exec-wait-und-exit.html" target="_blank" rel="noopener noreferrer">english version of a 2007 article&lt;/a>. In &lt;a href="news:de.comp.os.unix.linux.misc">de.comp.os.unix.linux.misc&lt;/a> somebody asked:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>Are commands in a script executed strictly sequentially, that is, will the next command only be executed when the previous command has completed, or will the shell &lt;strong>automatically&lt;/strong> start the next command if the system has spare capacity?&lt;/li>
&lt;li>Can I change the default behavior - whatever it may be - in any way?&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;p>If you are looking into the fine manual, it may explain at some point that the shell starts each command in a separate process. Then you may continue your thought process and ask what that actually means. As soon as you get to this stage, you may want to have a look at the Unix process lifecycle.&lt;/p>
&lt;h2 id="processes-and-programs">Processes and programs&lt;/h2>
&lt;p>A program in Unix is a sequence of executable instructions on a disk. You can use the command &lt;em>size&lt;/em> to get a very cursory check of the structure and memory demands of the program, or use the various invocations of &lt;em>objdump&lt;/em> for a much more detailed view. The only aspect that is of interest to us is the fact that a program is a sequence of instructions and data (on disk) that may potentially be executed at some point in time, maybe even multiple times, maybe even concurrently. Such a program in execution is called a process. The process contains the code and initial data of the program itself, and the actual state at the current point in time for the current execution. That is the memory map and the associated memory (check /proc/&lt;em>pid&lt;/em>/maps), but also the program counter, the processor registers, the stack, and finally the current root directory, the current directory, environment variables and the open files, plus a few other things (in modern Linux for example, we find the processes cgroups and namespace relationships, and so on - things became a lot more complicated since 1979). In Unix processes and programs are two different and independent things. You can run a program more than once, concurrently. For example, you can run two instances of the &lt;em>vi&lt;/em> editor, which edit two different texts. Program and initial data are the same: it is the same editor. But the state inside the processes is different: the text, the insert mode, cursor position and so on differ. From a programmers point of view, “the code is the same, but the variable values are differing”. A process can run more than one program: The currently running program is throwing itself away, but asks that the operating system loads a different program into the same process. The new program will inherit some reused process state, such as current directories, file handles, privileges and so on. All of that is done in original Unix, at the system level, with only four syscalls:&lt;/p>
&lt;ul>
&lt;li>&lt;code>fork()&lt;/code>&lt;/li>
&lt;li>&lt;code>exec()&lt;/code>&lt;/li>
&lt;li>&lt;code>wait()&lt;/code>&lt;/li>
&lt;li>&lt;code>exit()&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="usermode-and-kernel">Usermode and Kernel&lt;/h2>
&lt;p>&lt;a href="https://isotopp.github.io/uploads/prozesswechsel.png" target="_blank" rel="noopener noreferrer">Usermode and Kernel&lt;/a>&lt;/p>
&lt;p>&lt;em>Context switching: Process 1 is running for a bit, but at (1) the kernel interrupts the execution and switches to process 2. Some time later, process 2 is frozen, and we context switch back to where we left off with (1), and so on. For each process, this seems to be seamless, but it happens in intervals that are not continous.&lt;/em> Whenever a Unix process does a system call (and at some other opportunities) the current process leaves the user context and the operating system code is being activated. This is privileged kernel code, and the activation is not quite a subroutine call, because not only is privileged mode activated, but also a kernel stack is being used and the CPU registers of the user process are saved. From the point of view of the kernel function, the user process that has called us is inert data and can be manipulated at will. The kernel will then execute the system call on behalf of the user program, and then will try to exit the kernel. The typical way to leave the kernel is through the scheduler. The scheduler will review the process list and current situation. It will then decide into which of all the different userland processes to exit. It will restore the chosen processes registers, then return into this processes context, using this processes stack. The chosen process may or may not be the one that made the system call. In short: Whenever you make a system call, you may (or may not) lose the CPU to another process. That’s not too bad, because this other process at some point has to give up the CPU and the kernel will then return into our process as if nothing happened. Our program is not being executed linearly, but in a sequence of subjectively linear segments, with breaks inbetween. During these breaks the CPU is working on segments of other processes that are also runnable.&lt;/p>
&lt;h2 id="fork-and-exit">fork() and exit()&lt;/h2>
&lt;p>In traditional Unix the only way to create a process is using the &lt;code>fork()&lt;/code> system call. The new process gets a copy of the current program, but new process id (pid). The process id of the parent process (the process that called &lt;code>fork()&lt;/code>) is registered as the new processes parent pid (ppid) to build a process tree. In the parent process, &lt;code>fork()&lt;/code> returns and delivers the new processes pid as a result. The new process also returns from the &lt;code>fork()&lt;/code> system call (because that is when the copy was made), but the result of the &lt;code>fork()&lt;/code> is 0. So &lt;code>fork()&lt;/code> is a special system call. You call it once, but the function returns twice: Once in the parent, and once in the child process. &lt;code>fork()&lt;/code> increases the number of processes in the system by one. Every Unix process always starts their existence by returning from a &lt;code>fork()&lt;/code> system call with a 0 result, running the same program as the parent process. They can have different fates because the result of the &lt;code>fork()&lt;/code> system call is different in the parent and child incarnation, and that can drive execution down different &lt;code>if()&lt;/code> branches. In Code:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#include &lt;stdio.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;unistd.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;stdlib.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main(void) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid\_t pid = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid = fork();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid == 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the child.\\n");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid > 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the parent, the child is %d.\\n", pid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid &lt; 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> perror("In fork():");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> exit(0);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Running this, we get:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> make probe1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cc probe1.c -o probe1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> ./probe1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the child.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the parent, the child is 16959.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We are defining a variable &lt;code>pid&lt;/code> of the type &lt;code>pid_t&lt;/code>. This variable saves the &lt;code>fork()&lt;/code> result, and using it we activate one (“I am the child.”) or the other (“I am the parent”) branch of an if(). Running the program we get two result lines. Since we have only one variable, and this variable can have only one state, an instance of the program can only be in either one or the other branch of the code. Since we see two lines of output, two instances of the program with different values for &lt;code>pid&lt;/code> must have been running. If we called &lt;code>getpid()&lt;/code> and printed the result we could prove this by showing two different pids (change the program to do this as an exercise!). The &lt;code>fork()&lt;/code> system call is entered once, but left twice, and increments the number of processes in the system by one. After finishing our program the number of processes in the system is as large as before. That means there must be another system call which decrements the number of system calls. This system call is &lt;code>exit()&lt;/code>. &lt;code>exit()&lt;/code> is a system call you enter once and never leave. It decrements the number of processes in the system by one. &lt;code>exit()&lt;/code> also accepts an exit status as a parameter, which the parent process can receive (or even has to receive), and which communicates the fate of the child to the parent. In our example, all variants of the program call &lt;code>exit()&lt;/code> - we are calling &lt;code>exit()&lt;/code> in the child process, but also in the parent process. That means we terminate two processes. We can only do this, because even the parent process is a child, and in fact, a child of our shell. The shell does exactly the same thing we are doing:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bash (16957) --- calls fork() ---> bash (16958) --- becomes ---> probe1 (16958)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">probe1 (16958) --- calls fork() ---> probe1 (16959) --> exit()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> +---> exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;code>exit()&lt;/code> closes all files and sockets, frees all memory and then terminates the process. The parameter of &lt;code>exit()&lt;/code> is the only thing that survives and is handed over to the parent process.&lt;/p>
&lt;h2 id="wait">wait()&lt;/h2>
&lt;p>Our child process ends with an &lt;code>exit(0)&lt;/code>. The 0 is the exit status of our program and can be shipped. We need to make the parent process pick up this value and we need a new system call for this. This system call is &lt;code>wait()&lt;/code>. In Code:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#include &lt;stdio.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;unistd.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;stdlib.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;sys/types.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;sys/wait.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main(void) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid\_t pid = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int status;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid = fork();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid == 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the child.\\n");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sleep(10);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the child, 10 seconds later.\\n");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid > 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the parent, the child is %d.\\n", pid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid = wait(&amp;status);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("End of process %d: ", pid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (WIFEXITED(status)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("The process ended with exit(%d).\\n", WEXITSTATUS(status));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (WIFSIGNALED(status)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("The process ended with kill -%d.\\n", WTERMSIG(status));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid &lt; 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> perror("In fork():");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> exit(0);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And the runtime protocol:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> make probe2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cc probe2.c -o probe2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> ./probe2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the child.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the parent, the child is 17399.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the child, 10 seconds later.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">End of process 17399: The process ended with exit(0).&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The variable &lt;code>status&lt;/code> is passed to the system call &lt;code>wait()&lt;/code> as a reference parameter, and will be overwritten by it. The value is a bitfield, containing the exit status and additional reasons explaining how the program ended. To decode this, C offers a number of macros with predicates such as &lt;code>WIFEXITED()&lt;/code> or &lt;code>WIFSIGNALED()&lt;/code>. We also get extractors, such as &lt;code>WEXITSTATUS()&lt;/code> and &lt;code>WTERMSIG()&lt;/code>. &lt;code>wait()&lt;/code> also returns the pid of the process that terminated, as a function result. &lt;code>wait()&lt;/code> stops execution of the parent process until either a signal arrives or a child process terminates. You can arrange for a SIGALARM to be sent to you in order to time bound the &lt;code>wait()&lt;/code>.&lt;/p>
&lt;h2 id="the-init-program-and-zombies">The &lt;code>init&lt;/code> program, and Zombies&lt;/h2>
&lt;p>The program &lt;code>init&lt;/code> with the pid 1 will do basically nothing but calling &lt;code>wait()&lt;/code>: It waits for terminating processes and polls their exit status, only to throw it away. It also reads &lt;code>/etc/inittab&lt;/code> and starts the programs configured there. When something from &lt;code>inittab&lt;/code> terminates and is set to &lt;code>respawn&lt;/code>, it will be restarted by &lt;code>init&lt;/code>. When a child process terminates while the parent process is not (yet) waiting for the exit status, &lt;code>exit()&lt;/code> will still free all memory, file handles and so on, but the &lt;code>struct task&lt;/code> (basically the &lt;code>ps&lt;/code> entry) cannot be thrown away. It may be that the parent process at some point in time arrives at a &lt;code>wait()&lt;/code> and then we have to have the exit status, which is stored in a field in the &lt;code>struct task&lt;/code>, so we need to retain it. And while the child process is dead already, the process list entry cannot die because the exit status has not yet been polled by the parent. Unix calls such processes without memory or other resouces associated &lt;em>Zombies&lt;/em>. Zombies are visible in the process list when a process generator (a forking process) is faulty and does not &lt;code>wait()&lt;/code> properly. They do not take up memory or any other resouces but the bytes that make up their &lt;code>struct task&lt;/code>. The other case can happen, too: The parent process exits while the child moves on. The kernel will set the ppid of such children with dead parents to the constant value 1, or in other words: &lt;code>init&lt;/code> inherits orphaned processes. When the child terminates, &lt;code>init&lt;/code> will &lt;code>wait()&lt;/code> for the exit status of the child, because that’s what &lt;code>init&lt;/code> does. No Zombies in this case. When we observe the number of processes in the system to be largely constant over time, then the number of calls to &lt;code>fork()&lt;/code>, &lt;code>exit()&lt;/code> and &lt;code>wait()&lt;/code> have to balanced. This is, because for each &lt;code>fork()&lt;/code> there will be an &lt;code>exit()&lt;/code> to match and for each &lt;code>exit()&lt;/code> there must be a &lt;code>wait()&lt;/code> somewhere. In reality, and in modern systems, the situation is a bit more complicated, but the original idea is as simple as this. We have a clean fork-exit-wait triangle that describes all processes.&lt;/p>
&lt;h2 id="exec">exec()&lt;/h2>
&lt;p>So while &lt;code>fork()&lt;/code> makes processes, &lt;code>exec()&lt;/code> loads programs into processes that already exist. In Code:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#include &lt;stdio.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;unistd.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;stdlib.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;sys/types.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#include &lt;sys/wait.h>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main(void) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid\_t pid = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int status;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid = fork();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid == 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the child.\\n");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execl("/bin/ls", "ls", "-l", "/tmp/kris", (char \*) 0);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> perror("In exec(): ");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid > 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("I am the parent, and the child is %d.\\n", pid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pid = wait(&amp;status);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("End of process %d: ", pid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (WIFEXITED(status)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("The process ended with exit(%d).\\n", WEXITSTATUS(status));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (WIFSIGNALED(status)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> printf("The process ended with kill -%d.\\n", WTERMSIG(status));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (pid &lt; 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> perror("In fork():");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> exit(0);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The runtime protocol:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> make probe3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cc probe3.c -o probe3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> ./probe3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the child.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">I am the parent, the child is 17690.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total 36
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rwxr-xr-x 1 kris users 6984 2007-01-05 13:29 probe1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- 1 kris users 303 2007-01-05 13:36 probe1.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rwxr-xr-x 1 kris users 7489 2007-01-05 13:37 probe2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- 1 kris users 719 2007-01-05 13:40 probe2.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rwxr-xr-x 1 kris users 7513 2007-01-05 13:42 probe3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r-- 1 kris users 728 2007-01-05 13:42 probe3.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">End of process 17690: The process ended with exit(0).&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here the code of &lt;code>probe3&lt;/code> is thrown away in the child process (the &lt;code>perror("In exec():")&lt;/code> is not reached). Instead the running program is being replaced by the given call to &lt;code>ls&lt;/code>. From the protocol we can see the parent instance of &lt;code>probe3&lt;/code> waits for the &lt;code>exit()&lt;/code>. Since the &lt;code>perror()&lt;/code> after the &lt;code>execl()&lt;/code>is never executed, it cannot be an &lt;code>exit()&lt;/code> in our code. In fact, &lt;code>ls&lt;/code> ends the process we made with an &lt;code>exit()&lt;/code> and that is what we receive our exit status from in our parent processes &lt;code>wait()&lt;/code> call.&lt;/p>
&lt;h2 id="the-same-as-a-shellscript">The same, as a Shellscript&lt;/h2>
&lt;p>The examples above have been written in C. We can do the same, in &lt;code>bash&lt;/code>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> cat probe1.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#! /bin/bash --
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "Starting child:"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sleep 10 &amp;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "The child is $!"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "The parent is $$"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "$(date): Parent waits."
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wait
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "The child $! has the exit status $?"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "$(date): Parent woke up."
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kris@linux:/tmp/kris> ./probe1.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Starting child:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The child is 18071
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The parent is 18070
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Fri Jan 5 13:49:56 CET 2007: Parent waits.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The child 18071 has the exit status 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Fri Jan 5 13:50:06 CET 2007: Parent woke up.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="the-actual-bash">The actual bash&lt;/h2>
&lt;p>We can also trace the shell while it executes a single command. The information from above should allow us to understand what goes on, and see how the shell actually works.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">kris@linux:~> strace -f -e execve,clone,fork,waitpid bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kris@linux:~> ls
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">clone(Process 30048 attached
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">child\_stack=0, flags=CLONE\_CHILD\_CLEARTID|CLONE\_CHILD\_SETTID|SIGCHLD,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">child\_tidptr=0xb7dab6f8) = 30048
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">\[pid 30025\] waitpid(-1, Process 30025 suspended
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;unfinished ...>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">\[pid 30048\] execve("/bin/ls", \["/bin/ls", "-N", "--color=tty", "-T", "0"\],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">\[/\* 107 vars \*/\]) = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Process 30025 resumed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Process 30048 detached
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;... waitpid resumed> \[{WIFEXITED(s) &amp;&amp; WEXITSTATUS(s) == 0}\], WSTOPPED
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WCONTINUED) = 30048
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- SIGCHLD (Child exited) @ 0 (0) ---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Linux uses a generalization of the original Unix &lt;code>fork()&lt;/code>, named &lt;code>clone()&lt;/code>, to create child processes. That is why we do not see &lt;code>fork()&lt;/code> in a Linux system to create a child process, but a &lt;code>clone()&lt;/code> call with some parameters. Linux also uses a specialized variant of &lt;code>wait()&lt;/code>, called &lt;code>waitpid()&lt;/code>, to wait for a specific pid. Linux finally uses the &lt;code>exec()&lt;/code> variant &lt;code>execve()&lt;/code> to load programs, but that is just shuffling the paramters around. At the end of &lt;code>ls&lt;/code> (PID 30048) the process 30025 will wake up from the &lt;code>wait()&lt;/code> and continue.&lt;/p>
&lt;h2 id="original-code-what-windows-does-and-what-microsoft-thinks-about-linux">Original Code, what Windows does, and what Microsoft thinks about Linux&lt;/h2>
&lt;p>This text is based on &lt;a href="http://groups.google.com/group/de.comp.os.unix.linux.misc/msg/4035c67415f9bc09" target="_blank" rel="noopener noreferrer">a USENET article&lt;/a> I wrote a long time ago. &lt;a href="https://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/xec.c" target="_blank" rel="noopener noreferrer">Here&lt;/a> is the original C-code of the original &lt;code>sh&lt;/code> from 1979, with the &lt;code>fork()&lt;/code> system call. Search for &lt;code>case TFORK:&lt;/code>. Also, check out the programming style of Mr. Bourne - this is C, even if it does not look like it. The &lt;a href="https://isotopp.github.io/2007/01/07/fork-exec-wait-und-exit.html" target="_blank" rel="noopener noreferrer">original 2007 blog article&lt;/a>, has a followup article &lt;a href="https://isotopp.github.io/2007/01/07/fork-und-exec-vs-createprocess.html" target="_blank" rel="noopener noreferrer">on Windows CreateProcess()&lt;/a>, which has not been translated. When implementing &lt;code>fork()&lt;/code> in Windows as part of the WSL 1, Microsoft ran into a lot of problems with the syscall, and wrote an article about how they hate it, and why they think their &lt;code>CreateProcessEx()&lt;/code> (in Unix: &lt;code>spawn()&lt;/code>) would be better. The &lt;a href="https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf" target="_blank" rel="noopener noreferrer">PDF&lt;/a> makes a number of good points, but is still wrong. :-)&lt;/p>
&lt;br>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the author.&lt;/em>&lt;/p></content:encoded><author>Kristian Köhntopp</author><media:thumbnail url="https://percona.community/blog/2021/01/prozesswechsel_hu_fa884569eea78102.jpg"/><media:content url="https://percona.community/blog/2021/01/prozesswechsel_hu_4753faea88db1f1b.jpg" medium="image"/></item><item><title>Embracing the Stream</title><link>https://percona.community/blog/2020/12/10/embracing-the-stream/</link><guid>https://percona.community/blog/2020/12/10/embracing-the-stream/</guid><pubDate>Thu, 10 Dec 2020 10:33:29 UTC</pubDate><description>So this happened: CentOS Project shifts focus to CentOS Stream</description><content:encoded>&lt;p>So this happened: &lt;a href="https://lists.centos.org/pipermail/centos-announce/2020-December/048208.html" target="_blank" rel="noopener noreferrer">CentOS Project shifts focus to CentOS Stream&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>The future of the CentOS Project is CentOS Stream, and over the next year we’ll be shifting focus from CentOS Linux, the rebuild of Red Hat Enterprise Linux (RHEL), to CentOS Stream, which tracks just ahead of a current RHEL release. CentOS Linux 8, as a rebuild of RHEL 8, will end at the end of 2021. CentOS Stream continues after that date, serving as the upstream (development) branch of Red Hat Enterprise Linux.&lt;/p>&lt;/blockquote>
&lt;p>And a lot of people react like this: &lt;a href="https://twitter.com/nixcraft/status/1336348208184741888" target="_blank" rel="noopener noreferrer">&lt;/a> &lt;em>Oracle buys Sun: Solaris Unix, Sun servers/workstation, and MySQL went to /dev/null. IBM buys Red Hat: CentOS is going to >/dev/null. Note to self: If a big vendor such as Oracle, IBM, MS, and others buys your fav software, start the migration procedure ASAP. (&lt;a href="https://twitter.com/nixcraft/status/1336348208184741888" target="_blank" rel="noopener noreferrer">Tweet&lt;/a>)&lt;/em> So it seems my opinion is the unpopular one: CentOS switching to Stream is not bad at all. When you wanted to run Openstack on CentOS in 2015, you needed to enable &lt;a href="https://fedoraproject.org/wiki/EPEL" target="_blank" rel="noopener noreferrer">EPEL&lt;/a> to even begin an install. The first thing this did was literally replace every single package in the install. That was, because CentOS at that time was literally making Debian Stale look young. And we see similar problems with Ubuntu LTS, for what it’s worth. Ubuntu LTS comes out every 2 years, and that’s kind of ok-ish, but it lasts 5 years, which is nonsensical. It was not, in the past.&lt;/p>
&lt;h2 id="so-what-changed">So what changed?&lt;/h2>
&lt;p>Software Development. We have been moving to a platform based development approach, leveraging the wins from DevOps. “Kris, that’s corporate bullshit.” It’s not, though. Let me spell it out in plain for you.&lt;/p>
&lt;h3 id="programming-languages-are-platforms-powered-by-tools">Programming languages are platforms powered by tools&lt;/h3>
&lt;p>People these days do not program in an editor, with a compiler. They use Github or Gitlab, with many integrations, and a local IDE. They commit to a VCS (git, actually, the world converged on one single VCS), and trigger a bunch of things. Typechecks, Reformatters, Tests, but also Code Quality Metrics, and Security Scanners. Even starting a new programming language in 2020 is not as easy as it was in the past. Having a language is not enough, because you do not only need a language and maybe a standard library, but also a JetBrains Product supporting it, SonarQube support, XRay integration, gitlab-ci.yml examples and so on. Basically, there is a huge infrastructure system designed to support development and whatever you start needs to fit into it ,right from the start. That is, because we have come to rely on an entire ecosystem of tooling to make our developers faster, and to enforce uniform standards across the group. And that is a good thing, which can help us to become better programmers.&lt;/p>
&lt;h3 id="github-and-gitlab-are-tools-for-conversations-about-code-among-developers">Github and Gitlab are tools for conversations about code among developers&lt;/h3>
&lt;p>We also have come to rely on tooling to enable collaboration, and structured discussion about code, since we as programmers no longer work alone. A good part of the value of Gitlab, Github and similar is enabling useful cooperation between developers, in ways that Developers value. Another good part of the value is extracted at the production end of these platforms: We produce artifacts of builds, automatically and in reproducible ways. Which includes also knowing things about these artifacts - for example, what went into producing them and being able to report on these things:&lt;/p>
&lt;ul>
&lt;li>Dependencies&lt;/li>
&lt;li>Licenses&lt;/li>
&lt;li>Versions&lt;/li>
&lt;li>Vulnerabilities&lt;/li>
&lt;li>Commit frequency and time to fix for each dependency, abandonware alert&lt;/li>
&lt;/ul>
&lt;p>and many more things. With these processes, and repositories, and with one other ingredient, we have made rollouts and rollbacks an automated and uniform procedure, provided we find a way to manage and evolve state properly. Compared to the hand crafted bespoke rollout and rollback procedures of the 2010s, this is tremendous progress.&lt;/p>
&lt;h3 id="immutable-infrastructure-and-reproducible-builds">Immutable infrastructure, and reproducible builds&lt;/h3>
&lt;p>This other ingredient is immutable infrastructure. It is the basic idea that we do no longer manipulate the state of the base image we run our code on, ever, after it is deployed. It’s basically death to Puppet and its likes. Instead we change the build process, producing immutable images, and quickly rebuild and redeploy. We deploy the base image, and then supply secrets, runtime config and control config in other, more appropriate ways. Things like Vault, a consensus system such as Zookeeper, or similar mechanisms come to mind. It allows us to orchestrate change across a fleet of instances, all alike, in a way that guarantees consistency across our fleet, in an age where all computing has become distributed computing. The same thinking can be applied to the actual base operating system of the host, where we remove application installs completely from the base operating system. Instead we provide a mechanism to mount and unmount application installs, including their dependencies, in the form of virtual machine images, container images or serverless function deployments (also containers, but with fewer buttons). As a consequence, everything becomes single-user, single-tenant - one image contains only Postgres, another one only your static images webserver (images supplied from an external mountable volume), and a third one only your production Python application plus runtime environment. With only one thing in the container, Linux UIDs no longer have a useful separation function, and other isolation and separation mechanisms take their place:&lt;/p>
&lt;ul>
&lt;li>virtualization,&lt;/li>
&lt;li>CGroups,&lt;/li>
&lt;li>Namespaces,&lt;/li>
&lt;li>Seccomp,&lt;/li>
&lt;/ul>
&lt;p>and similar. They are arguably more powerful, anyway. This also forms a kind of argument in the great “Is curlbash or even sudo curlbash still a bad thing?” debate of our times, but I am unsure which (I’m not: in a single-user single-tenant environment curlbashing into that environment should not be a security problem, but you get problems proving the provenance of your code. Which you would not have, had you used another, less casual method of acquiring that dependency).&lt;/p>
&lt;h3 id="images-as-building-blocks-for-applications">Images as building blocks for applications&lt;/h3>
&lt;p>So now we can use entire applications, with configuration provided and injected at runtime, to construct services, and we can add relatively tiny bits of our own code to build our own services on top of existing services, provided by the environment. We get Helm Charts for Kubernetes, we get &lt;a href="https://www.infoq.com/articles/serverless-sea-change/" target="_blank" rel="noopener noreferrer">The Serverless Sea Change&lt;/a>, and Step Functions. We also get Nocode, Codeless or similar attempts at building certain things only from services without actual coding. But it is more pervasive than this:&lt;/p>
&lt;ul>
&lt;li>The Unifi Control Plane uses multiple Java processes and one Mongodb. It can be dockered into one container, or can be provided as helm chart or as a docker-compose with multiple containers, for better scalability and maintenance.&lt;/li>
&lt;li>The gitlab Omnibus uses a single container, again, with Postgres, Redis and a lot of internal state plus Chef to deploy about a dozen components, but differentiated deploys for the individual components in a K8s context also exist.&lt;/li>
&lt;li>Things like a Jitsi setup can be packaged into a single, relatively simple docker-compose.yml, and will assemble themselves from images mostly automatically. The result will run on almost any operating system substrate, as long as it provides a Linux kernel syscall interface.&lt;/li>
&lt;/ul>
&lt;h3 id="fighting-conways-law">Fighting Conway’s law&lt;/h3>
&lt;p>At that is kind of the point: By packing all dependencies into the container or VM image itself, the base operating system hardly matters any more. It allows us to move on, each on their own speed, on a per-project basis. The project will bring its own database, cache, runtime and libraries with itself, without version conflicts, and without waiting for the distro to upgrade them, or to provide them at all. Conversely it allows the Distro to move to Stream: They are finally free from slow moving OSS projects preventing them from upgrading local components, because one of them is not yet ready to move. Even teams in the Enterprise are now free to move at their own speed, because they no longer have to wait for half a dozen stakeholders ot get to the Technical Debt Section of their backlog. The main point is, in my opinion, that it is okay and normal for the application to use a different “No longer a full OS” than what the host uses. In acknowledging that both can reduce scope and size, and optimize. This is a good thing, and will speed up development. So in a world where components and their dependencies are being packaged as single-user single-tenancy units of execution (virtual machines, containers and the like), CentOS moving to Streams is not only acknowledging that change, it also forced the slower half of the world to acknowledge this, and to embrace it. I say: This is a good thing. And if you rant “Stability goes out of the window!” - check your calendar and your processes. It’s 2020. Act like it. One of the major innovations in how we do computers in the last decade has been establishing the beginnings of a certifiable process for building the things we run. Or, as &lt;a href="https://isotopp.github.io/Christoph%20Petrausch" target="_blank" rel="noopener noreferrer">Christoph Petrausch&lt;/a> puts it in &lt;a href="https://twitter.com/hikhvar/status/1336608880013488130" target="_blank" rel="noopener noreferrer">this tweet&lt;/a>: “If your compliance is based on certifying the running end product instead of the process that built it, your organisation will not be able to keep up with the development speed of others.”  &lt;/p>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the author.&lt;/em>&lt;/p></content:encoded><author>Kristian Köhntopp</author><category>centos</category><category>DevOps</category><category>GitHub</category><category>Gitlab</category><category>koehntopp</category><category>Linux</category><category>Open Source Databases</category><category>RedHat</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/12/stream-migrate-now_hu_50c1668258a508b7.jpg"/><media:content url="https://percona.community/blog/2020/12/stream-migrate-now_hu_45909977bd60fdc1.jpg" medium="image"/></item><item><title>Fixing Common PostgreSQL Performance Bottlenecks</title><link>https://percona.community/blog/2020/12/04/fixing-common-postgresql-performance-bottlenecks/</link><guid>https://percona.community/blog/2020/12/04/fixing-common-postgresql-performance-bottlenecks/</guid><pubDate>Fri, 04 Dec 2020 20:26:50 UTC</pubDate><description>Overview In this article, I look at how poorly designed sharding systems and replication systems in PostgreSQL affect query performance in high volume situations, and how to ensure data consistency across many servers. I also discuss how excessive vacuuming generates I/O traffic, and how connection pooling is used to improve transaction throughput by caching connections from clients. I also cover how insufficient memory could affect PostgreSQL performance. These are key problems I have encountered as a database consultant, and how I’ve overcome them.</description><content:encoded>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>In this article, I look at how poorly designed sharding systems and replication systems in PostgreSQL affect query performance in high volume situations, and how to ensure data consistency across many servers.  I also discuss how excessive vacuuming generates I/O traffic, and how connection pooling is used to improve transaction throughput by caching connections from clients.  I also cover how insufficient memory could affect PostgreSQL performance.  These are key problems I have encountered as a database consultant, and how I’ve overcome them.&lt;/p>
&lt;h2 id="sharding-bottlenecks">Sharding Bottlenecks&lt;/h2>
&lt;p>Sharding is one of the ways to scale a database server to store terabytes of data. Sharding distributes data evenly via a partitioning function or algorithm to different tables or to a different server instance.&lt;/p>
&lt;p>The following are common sharding approaches for production systems:&lt;/p>
&lt;ul>
&lt;li>Sharding by geography&lt;/li>
&lt;li>Sharding by username&lt;/li>
&lt;li>Sharding by user-id&lt;/li>
&lt;li>Sharding by a range&lt;/li>
&lt;/ul>
&lt;p>Although sharding is one of the most common strategies to handle a large number of queries from clients, a poorly designed sharding system can affect database performance and become a performance bottleneck.&lt;/p>
&lt;p>Let’s assume we have a database made up of two tables such as the users’ table and followers table. The users’ table contains registered users of a social site service while the followers’ table is made up of users being followed by users in the users’ table.&lt;/p>
&lt;p>Since we have approximately 10,000 users subscribing to our social site service and most of the subscribed users do not have many followers, we decided to shard the users’ table by username (indexing the username as the query key). Each sharded table is assigned to 2,000 users. Thus all 10,000 users are assigned to five (5) tables.&lt;/p>
&lt;p>It is advisable to shard large tables instead of smaller tables.&lt;/p>
&lt;p>Now let’s look at why the sharding approach we selected can affect query performance and become a bottleneck in database infrastructure.&lt;/p>
&lt;p>In a situation where a follower sends a query or request for a particular user’s details (let’s say username, Frank Brown). Assuming there are more than five users with the first name Frank. The query engine needs to go through multiple names with the first name ‘Frank’ before arriving at the user with the last name ‘Brown’.&lt;/p>
&lt;p>Also because a username is updateable or a mutable value, we need to perform an update operation to make sure every old username in the users’ table is replaced with a new username.&lt;/p>
&lt;p>Performing an update operation for many users in a relational database is really expensive as we need to do so across many tables. In this case, even though we decided to introduce scalability by sharding the users’ table, we failed to design effective sharding.&lt;/p>
&lt;p>Instead of sharding user tables by usernames in a heavy data-intensive environment, it is advisable to shard by geographical area. Users from a specific region or continent are assigned to a specific table. For instance, we can assign subscribed users from the EMEA region to a specific table and other groups of subscribed users to the LATAM region.&lt;/p>
&lt;p>Although the geographical area is a mutable value likewise username, it does not introduce redundancy. It is not mandatory to update a user’s location. We can still maintain the same geographical value for a user even if the user migrates from one location to another.&lt;/p>
&lt;p>In addition, we can also improve query optimization by storing each value in a single column. In this case, the first name and the last name of a user is stored in different or separate columns. There is no need for the query engine to go through multiple users with username ‘Frank’.&lt;/p>
&lt;p>Storing two values in a single column makes it difficult or almost impossible to run an efficient query.&lt;/p>
&lt;h2 id="replication-bottlenecks">Replication Bottlenecks&lt;/h2>
&lt;p>Building a high data-intensive service requires a well-designed replication system too. The best way to set up a replication system is to align it with the design of a web service. In other words, the structure of the web service should determine which replication concept to use.&lt;/p>
&lt;p>Most relational database systems follow the concept of asynchronous and synchronous replication.&lt;/p>
&lt;p>With Asynchronous replication, data is replicated or copied to the slave server once the transaction has been committed on the master server. Synchronous replication ensures that data written by the transaction will be on both the master server and the slave server at the time the transaction commits.&lt;/p>
&lt;p>Let assume we decided to use a synchronous replication system for a write-intensive web service. This requires the same set of data to exist on both the master and the slave server at the time the transaction commits. But this can lead to latency issues.&lt;/p>
&lt;p>Synchronous replication is far more expensive than asynchronous replication because of the overhead involved. Data usually is replicated on more than two servers at the time the transaction commits.&lt;/p>
&lt;p>How do we ensure data consistency and yet prevent latency issues?&lt;/p>
&lt;p>In setting up a replication system for a write-intensive service, the best solution is to implement asynchronous replication. It is less expensive since since no overhead involved. The master server does not need to connect to two or three remote replica servers to replicate data at the time the transaction commits. Instead, data is replicated on the slave servers after the transaction has been committed on the master.&lt;/p>
&lt;p>We can sometimes rely on XLOG to replay all transaction in case the master server experiences partition failure. Partition failure or ‘split-brain’ separates the master server from the slave servers so there is no longer communication between them.  However, it is advisable to also provide external backups for the master server instead of relying solely on XLOG.&lt;/p>
&lt;p>In PostgreSQL, it possible to backup data on a master server to an external archive using the archive command. There is also a restore command to restore the master server to its previous state. Although this procedure (asynchronous replication + continuous archiving) is complex to administer, it guarantees efficient performance than synchronous replication. Also continuous archiving does not consume excess I/O capacity.&lt;/p>
&lt;p>Choosing whether to use synchronous replication or asynchronous replication should be determined by the design of the service communicating with the database.&lt;/p>
&lt;h2 id="checkpointing-bottlenecks">Checkpointing Bottlenecks&lt;/h2>
&lt;p>In PostgreSQL, data protection and consistency are assured through the XLOG. Any data written to postgres is sent to the XLOG before it is written to the data files. It is impossible to write data to the XLOG forever without taking up or filing up disk space, checkpointing needs to be done.&lt;/p>
&lt;p>Thus checkpointing is the process of deleting or truncating XLOG after a specified period. However, if the &lt;code>checkpoint_segment&lt;/code> and &lt;code>checkpoint_time&lt;/code> parameters in the &lt;code>postgres.conf&lt;/code> file is not tuned well, we have a problem at hand.&lt;/p>
&lt;p>For instance, if the distance between two checkpoints is very large and in the event of a system crash, postgres has to replay the last checkpoint, the failed database instance might take a long time to start again.&lt;/p>
&lt;p>When it comes to checkpointing, the following parameters inside the postgres.conf file are very important. Balanced configuration of these two parameters is a must.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>checkpoint_segment&lt;/strong>: In addition to the &lt;code>checkpoint_timeout&lt;/code>, this parameter defines the distance between two checkpoints. In Postgres, a segment is 16MB by default. In production systems, it is safe to set checkpoint_segment to 256MB.&lt;/li>
&lt;li>&lt;strong>checkpoint_timeout&lt;/strong>: It defines the upper limit of time allowed between two checkpoints. You can increase it to about 30min.&lt;/li>
&lt;/ul>
&lt;h2 id="bloat-bottlenecks">Bloat Bottlenecks&lt;/h2>
&lt;p>In PostgreSQL, autovacuum is used for dealing with dead rows and frozen rows. Dead rows are rows that have been deleted or become obsolete but they have not been physically removed from tables. Frozen rows occur when a row version is old enough to become a candidate for being frozen.&lt;/p>
&lt;p>In PostgreSQL, we make use of the VACCUM command to deal with dead rows and frozen rows. Although vacuuming is the best way to deal with dead and frozen rows, it creates a lot of I/O traffic. In order to prevent high disk I/O, it is often necessary to turn off vacuuming by modifying the autovacuum parameter in the postgres.conf file.  But bloated tables and indexes can occur when you turn off autovaccum for a longer time than usual. Bloated tables and indexes tend to affect database performance because they occupy more storage space.&lt;/p>
&lt;p>Postgresql provides extension such as pgstattuple to deal with bloated tables and indexes. The function pgstattuple can be used to examine row-level statistics to determine or find out if there are dead rows available. If dead rows are present, the dead tuples percent column value is greater than zero. If there are no dead rows, the value is less than 1.&lt;/p>
&lt;p>The following steps show how to discover and remove dead rows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">EXTENSION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pgstattuple&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">m_data&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">pgstattuple&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'m_data'&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">DELETE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">m_data&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">generate_series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">pgstattuple&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'m_data'&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">VACCUM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">m_data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">stats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">pgstattuple&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'m_data'&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can run the following query to check if there are any bloating indexes for a particular table.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">relname&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_table_size&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">oid&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">index_size&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">pgstatindex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">relname&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">avg_leaf_density&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">bloat_ratio&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pg_class&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">relname&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'casedemo'&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">relkind&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'i'&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="vacuuming-bottlenecks">Vacuuming Bottlenecks&lt;/h2>
&lt;p>Although autovaccum or vacuum is used to remove dead rows, it is also used for the following:&lt;/p>
&lt;ul>
&lt;li>To keep statistics collection up to date which is used by the PostgreSQL query planner.&lt;/li>
&lt;li>To prevent the loss of old transaction data due to transaction ID wraparound issues.&lt;/li>
&lt;/ul>
&lt;p>Although autovacuum or vacuum can be used to physically remove dead rows to prevent bloated tables, excessive vacuuming can cause a database to perform below par because it generates high I/O traffic.&lt;/p>
&lt;p>There are two situations where there might be high I/O traffic during vacuuming:&lt;/p>
&lt;ol>
&lt;li>When a table is made up of a large number of rows that need freezing.&lt;/li>
&lt;li>When there are many rows with the same transaction ID during freezing time.&lt;/li>
&lt;/ol>
&lt;p>In order to avoid excessive vaccuming, you need to focus on the following parameters in the &lt;code>postgresql.conf&lt;/code> file.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>autovaccum_vaccum_threshold&lt;/strong>: This parameter determines the number of updated and deleted rows to initiate VACCUM in the associated table. You can set the value to 100. Thus 100 rows of updated and deleted rows will be vaccumed.&lt;/li>
&lt;li>&lt;strong>autovaccum_analyze_threshold&lt;/strong>: This parameter determines the number of updated and deleted rows to initiate ANALYZE in the associated table.&lt;/li>
&lt;li>&lt;strong>autovaccum_analyze_threshold&lt;/strong>: This parameter specifies the number of updated and deleted rows you need to initiate ANALYZE in the associated table. You can set this value to 100.&lt;/li>
&lt;li>&lt;strong>autovaccum_max_workers&lt;/strong>: This parameter specifies the number of workers that might be executed during vaccuming. A maximum of 5 workers is is enough to avoid excessive use of OS resources.&lt;/li>
&lt;li>&lt;strong>autovaccum_vaccum_scale_factor&lt;/strong>: This parameter specifies the fraction of the table size that needs to be added to autovaccum_vaccum_threshold when deciding whether to trigger VACCUM. You can set it value to 0.4.&lt;/li>
&lt;li>&lt;strong>autovaccum_analyze_scale_factor&lt;/strong>: This parameter specifies the fraction of the table size that needs to be added to autovaccum_vaccum_threshold when deciding whether to trigger ANALYZE. You can set it value to 0.3.&lt;/li>
&lt;/ul>
&lt;p>Apart from modifying the above parameters to suit your database behavior, you need to select tables with a large number of updated and deleted rows to vacuum frequently. In addition, it is advisable to vacuum after working hours or on weekends. You can schedule vacuuming using the &lt;a href="https://github.com/citusdata/pg_cron" target="_blank" rel="noopener noreferrer">pg_cron&lt;/a> extension. For instance, the code below executes the VACCUM command at 10:00 pm every Sunday.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cron&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">schedule&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">'0 10 * * 7'&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">'VACCUM'&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="connection-bottleneck">Connection Bottleneck&lt;/h2>
&lt;p>If your application is designed around ‘short-lived’ connections and you expect many queries from different client sessions, then you need to implement connection pooling using the likes of pgbouncer, pgpool2, and so on.&lt;/p>
&lt;p>Why is connection pooling important?&lt;/p>
&lt;p>Connection pooling creates a pool of connections and caches or reserves those connections so that it can be used again. In PostgreSQL, there is a process known as postmaster which handles or manages communication between frontend and backend processes. The postmaster process starts another separate server process known as Postgres to handle connections from clients.&lt;/p>
&lt;p>Each time a client connects to the Postgres database, the postmaster process spawns or creates a new process for each connection to the database. This process takes up to 2 or 3 MB memory for every connection to the database.&lt;/p>
&lt;p>So imagine a database infrastructure without any connection pooling? More memory is consumed for creating these connections.&lt;/p></content:encoded><author>Michael Aboagye</author><category>Michael.Aboagye</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2020/08/social-forums-postgresql-general-discussion_hu_cf4837590d47f2ec.jpg"/><media:content url="https://percona.community/blog/2020/08/social-forums-postgresql-general-discussion_hu_86240c1b83baccc1.jpg" medium="image"/></item><item><title>Not JOINing on PERFORMANCE_SCHEMA</title><link>https://percona.community/blog/2020/12/01/not-joining-on-performance_schema/</link><guid>https://percona.community/blog/2020/12/01/not-joining-on-performance_schema/</guid><pubDate>Tue, 01 Dec 2020 19:22:46 UTC</pubDate><description>The tables in PERFORMANCE_SCHEMA (P_S) are not actually tables. You should not think of them as tables, even if your SQL works on them. You should not JOIN them, and you should not GROUP or ORDER BY them.</description><content:encoded>&lt;p>The tables in &lt;code>PERFORMANCE_SCHEMA&lt;/code> (&lt;code>P_S&lt;/code>) are not actually tables. You should not think of them as tables, even if your SQL works on them. You should not JOIN them, and you should not GROUP or ORDER BY them.&lt;/p>
&lt;h2 id="unlocked-memory-buffers-without-indexes">Unlocked memory buffers without indexes&lt;/h2>
&lt;p>The stuff in &lt;code>P_S&lt;/code> has been created with “keep the impact on production small” in mind. That is, from a users point of view, you can think of them as unlocked memory buffers - the values in there change as you look at them, and there are precisely zero stability guarantees. There are also no indexes.&lt;/p>
&lt;h3 id="unstable-comparisons">Unstable comparisons&lt;/h3>
&lt;p>When sorting a table for a GROUP BY or ORDER BY, it may be necessary to compare the value of one row to other rows multiple times in order to determine where the row goes. The value compared to other rows can change while this happens, and will change more often the more load the server has. The end result is unstable. Also, as the table you sort may be larger on a server under load, the row may need more comparisons, making this even more likely to happen. The table you look at may produce correct results on your stable, underutilized test systems, but the monitoring you base on this will fail on a loaded test system. Do not use GROUP BY or ORDER BY on &lt;code>P_S&lt;/code> tables.&lt;/p>
&lt;h3 id="no-indexes-meaning-slow-joins-on-loaded-systems">No indexes, meaning slow joins on loaded systems&lt;/h3>
&lt;p>When JOINing a &lt;code>P_S&lt;/code> table against other tables, the join is done without indexes. There are no indexes defined in &lt;code>P_S&lt;/code>, and if there were they would make updates to values in &lt;code>P_S&lt;/code> more expensive, which is against the initial design tenet - “keep the impact on production small”. In practice that means your join against the processlist or session variables tables in &lt;code>P_S&lt;/code> do little harm in test, but will fail in production environments with many connections. You will be losing monitoring the moment you need it most - under load, in critital situations. Do not JOIN &lt;code>P_S&lt;/code> tables to anything.&lt;/p>
&lt;h2 id="how-to-monitor">How to monitor&lt;/h2>
&lt;p>About the only type of query you can successfully run on &lt;code>P_S&lt;/code> is a single table &lt;code>SELECT * FROM P_S.table&lt;/code>, maybe with a simple &lt;code>WHERE&lt;/code> clause. That is, you can download and materialize data from a single &lt;code>P_S&lt;/code> table at a time, unsorted, unaggregated. Connection to other tables, aggregation and sorting have to be done on tables that are not &lt;code>P_S&lt;/code> tables. There are multiple ways to do this.&lt;/p>
&lt;h3 id="subqueries-without-optimization">Subqueries, without optimization&lt;/h3>
&lt;p>It used to be that the MySQL optimizer did not resolve simple subqueries properly. So&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> select &lt;complicated stuff> from
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -> ( select * from performance_schema.sometable ) as t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -> order by &lt;something>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>used to work. The subquery &lt;code>t&lt;/code> would materialize the &lt;code>P_S&lt;/code> table as whatever your version of MYSQL used for implicit temporary tables, and the rest of the query resolution would happen on the materialized temptable. This is a snapshot, and would be stable. It still would not have indexes. And it still would not add up to 100%, of course. That is, queries like Dennis Kaarsemakers “How loaded is the SQL_THREAD” Replication Load analysis never came out at 100%, because the various values changed while the temporary table would be materialized, so you do not get a consistent snapshot (and by construction, this kind of consistency is impossible in &lt;code>P_S&lt;/code>). Anyway, with older versions of MySQL, this results in the query plan we want. Since MySQL 5.7, this does no longer work:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> select version();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| version() |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 8.0.22 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set (0.00 sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql> explain select * from ( select * from processlist ) as t;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | SIMPLE | processlist | NULL | ALL | NULL | NULL | NULL | NULL | 256 | 100.00 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 row in set, 1 warning (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Newer MySQL (5.7 and above) will apply the &lt;code>derived_merge&lt;/code> optimization and fold the subquery into the outer query, resulting in a rewritten single query that again is executed on &lt;code>P_S&lt;/code> directly. You either need to &lt;code>SET SESSION optimizer_switch = "derived_merge=off";&lt;/code> or provide an advanced &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html#optimizer-hints-table-level" target="_blank" rel="noopener noreferrer">MySQL 8 optimizer hint&lt;/a> to prevent the optimizer from ruining your cunning plan:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">mysql> explain select /*+ NO_MERGE(t) */ * from ( select * from processlist ) as t;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 1 | PRIMARY | &lt;derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 256 | 100.00 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 2 | DERIVED | processlist | NULL | ALL | NULL | NULL | NULL | NULL | 256 | 100.00 | NULL |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here we get the &lt;code>DERIVED&lt;/code> table as a non-&lt;code>P_S&lt;/code> temptable, and then run our “advanced” SQL on that as &lt;code>PRIMARY&lt;/code> on it.&lt;/p>
&lt;h3 id="in-the-client">In the client&lt;/h3>
&lt;p>The alternative is, of course, to completely download the tables in question into client side hashes, and then perform the required operations on them on the client side, in memory. The important thing here is to limit the amount of memory spent - do not download unconstrained result sets into your client monitoring program. Then use a linearly scaling join method to construct the connections between the tables. Effectively, load data into hashes, and then program a client side hash join. This is additive (n + m) instead of quadratic (n * m), so you can survive this. This is the recommended method.&lt;/p>
&lt;h2 id="who-is-doing-it-wrong">Who is doing it wrong?&lt;/h2>
&lt;p>Getting monitoring queries that use &lt;code>P_S&lt;/code> wrongly is common - it understands SQL, it handles &lt;code>SHOW CREATE TABLE&lt;/code>, so it is treated as a table and exposed to full SQL all the time. And on idle test boxen, it even looks like it works. At work, see this in our own code (still using a deprecated Diamond collector) and in SolarWinds nee Vividcortex. SolarWinds kindly highlights itself:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">sql&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- Most time consuming query - Coming from solar winds monitoring itself ¯_(ツ)_/¯
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ifnull&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">sql_text&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ifnull&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">ifnull&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_host&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">events_statements_history&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">left&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">using&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="o">`=?&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">event_id&lt;/span>&lt;span class="o">`=?&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Coming from the "table ownership write identifier".
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">cnt&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">digest_text&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">current_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">system_user&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">events_statements_history&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">esh&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">inner&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="o">`=`&lt;/span>&lt;span class="n">esh&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">event_name&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(...)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">current_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(...)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">digest_text&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">current_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- Coming from diamond collector
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">sbt&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">variable_value&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">status_by_thread&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">sbt&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">performance_schema&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">threads&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">using&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">thread_id&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">sbt&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">variable_name&lt;/span>&lt;span class="o">`=?&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">is&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">processlist_user&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">variable_value&lt;/span>&lt;span class="o">`&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Many of the above examples fail in multiple ways: Using JOIN for bad scalability (this is how we spotted them), by also using unstable sorting. We also see ORDER BY statements in the &lt;a href="https://github.com/influxdata/telegraf/blob/master/plugins/inputs/mysql/mysql.go#L376" target="_blank" rel="noopener noreferrer">Telegraf MySQL plugin&lt;/a> in one place. It uses LIMIT, but if the ORDER BY does not work (ie does not actually sort), you cut off randomly.&lt;/p>
&lt;h2 id="is-performance_schema-broken">Is PERFORMANCE_SCHEMA broken?&lt;/h2>
&lt;p>Clearly, it is not. Just badly misunderstood. The alternative is &lt;code>INFORMATION_SCHEMA&lt;/code>, which often locks, and that can be actually deadly: Just &lt;code>select * from INFORMATION_SCHEMA.INNODB_BUFFER_PAGE&lt;/code> on a server with a few hundreds of GB of buffer pool, humming at 10k QPS. The query will freeze the server completely for the runtime of the query – which with a large buffer pool size can be substantial. I’d rather have this in &lt;code>P_S&lt;/code> and then deal with the vagaries of the data changing while I read it than lose an important production server.&lt;/p>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the author.&lt;/em>&lt;/p></content:encoded><author>Kristian Köhntopp</author><category>MySQL</category><category>Open Source Databases</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/12/Screenshot-2020-12-01-at-23.21.51_hu_88a6bdf0c99058c4.jpg"/><media:content url="https://percona.community/blog/2020/12/Screenshot-2020-12-01-at-23.21.51_hu_146e54f3884427f0.jpg" medium="image"/></item><item><title>On the Observability of Outliers</title><link>https://percona.community/blog/2020/11/23/on-the-observability-of-outliers/</link><guid>https://percona.community/blog/2020/11/23/on-the-observability-of-outliers/</guid><pubDate>Mon, 23 Nov 2020 17:41:16 UTC</pubDate><description>At work, I am in an ongoing discussion with a number of people on the Observability of Outliers. It started with the age-old question “How do I find slow queries in my application?” aka “What would I want from tooling to get that data and where should that tooling sit?”</description><content:encoded>&lt;p>At work, I am in an ongoing discussion with a number of people on the Observability of Outliers. It started with the age-old question “How do I find slow queries in my application?” aka “What would I want from tooling to get that data and where should that tooling sit?”&lt;/p>
&lt;blockquote>
&lt;p>As a developer, I just want to automatically identify and isolate slow queries!&lt;/p>&lt;/blockquote>
&lt;p>Where I work, we do have &lt;a href="https://www.solarwinds.com/database-performance-monitor" target="_blank" rel="noopener noreferrer">SolarWinds Database Performance Monitor&lt;/a> aka Vividcortex to find slow queries, so that helps. But that collects data at the database, which means you get to see slow queries, but maybe not application context. There is also work done by a few developers which instead collects query strings, query execution times and query counts at the application. This has access to the call stack, so it can tell you which code generated the query that was slow. It also channels this data into events (what we have instead of &lt;a href="https://www.honeycomb.io/" target="_blank" rel="noopener noreferrer">Honeycomb&lt;/a>), and that is particularly useful, because now you can generate aggregates and keep the link from the aggregates to the constituting events.&lt;/p>
&lt;h2 id="how-do-you-find-outliers">How do you find outliers?&lt;/h2>
&lt;p>“That’s easy”, people will usually say, and then start with the average plus/minus one standard deviation. “We’ll construct this “n stddev wide corridor around the average” and then look at all the things outside.”&lt;/p>
&lt;p>&lt;a href="https://isotopp.github.io/uploads/2020/11/obs-no.png" target="_blank" rel="noopener noreferrer">&lt;/a>&lt;/p>
&lt;p>&lt;em>No.&lt;/em> That is descriptive statistics for normal distributions and for them to work we need to actually have a normal distribution. Averages and Standard Deviations work on normal distributions. So the first thing we need to do is to look at the data and ensure that we actually have a normal distribution.&lt;/p>
&lt;p>&lt;a href="https://isotopp.github.io/uploads/2020/11/obs-anscombe.png" target="_blank" rel="noopener noreferrer">&lt;/a>&lt;/p>
&lt;p>&lt;em>Anscombe’s Quartet is a set of graphs having an identical number of points, and producing identical descriptive statistics, but being clearly extremely different distributions.&lt;/em>
Because when you apply the Descriptive Statistics of Averages and Standard Deviations to things that are Not a Normal Distribution (see &lt;a href="https://en.wikipedia.org/wiki/Anscombe%27s_quartet" target="_blank" rel="noopener noreferrer">Anscombe’s Quartet&lt;/a>) they do not tell you much about the data: all the graphs in the infamous Quartet have the same descriptive stats (more than just average and stddev, even), but are clearly completely different. So what we would want is a graph of the data. For a time series – which is what we usually get when dealing with metrics – a good way to plot the data is a heatmap. For the given problem, the heatmap more often than not looks like this:&lt;/p>
&lt;p>&lt;a href="https://isotopp.github.io/uploads/2020/11/obs-heatmap.png" target="_blank" rel="noopener noreferrer">&lt;/a>&lt;/p>
&lt;p>&lt;em>We partition the time axis into buckets of - say - 10s each, and then bucket execution times linearly or logarithmically. For each query we run, we determine the bucket it goes into and increment by one. The resulting numbers are plotted as pixels - darker, redder means more queries in that bucket. A flat 2D plot of three dimensional data.&lt;/em> What you see here is a bi- or multipartite distribution. It is a common case when benchmarking: We have a (often larger) number of normally executed queries, and a second set (often smaller) of queries that need our attention because they are executed slower. The slow set is also often run with unstable execution times – an important secondary observation.&lt;/p>
&lt;p>&lt;a href="https://isotopp.github.io/uploads/2020/11/obs-mixture.png" target="_blank" rel="noopener noreferrer">&lt;/a>&lt;/p>
&lt;p>This is not a normal distribution, but a thing composed of two other things (hence bipartite), each of which in itself hopefully can be adequately modelled as a normal distribution: A &lt;a href="https://en.wikipedia.org/wiki/Mixture_model#Gaussian_mixture_model" target="_blank" rel="noopener noreferrer">gaussian mixture&lt;/a>. Luckily we do not actually have to deal with the math of these mixtures (I hope you did not follow the Wikipedia link :-) ) when we want to find slow queries. We just want to be able to separate them, which could even be done manually, and then want the back pointer to the events that constitute the cluster of outliers we identified.&lt;/p>
&lt;h2 id="unstable-execution-times">Unstable execution times&lt;/h2>
&lt;p>I mentioned above:“They are also often run with unstable execution times – an important secondary observation.” Slow queries are often slow because they cannot use indexes. When a tree index can be used, the number of comparisons needed to find the elements we are searching for is some kind of log of the table size. The end result is usually 4 – there are 3-5 lookups&lt;strong>¹&lt;/strong> needed in about any tree index to do a point lookup of the first element of a result. That means that the execution time for any query using proper indexes is usually extremely stable. When indexes cannot be used, the lookup times are scan times – linear functions of the result position or size. This varies a lot more, and so we get much more variable execution times for slow queries, and the jitter makes it only worse: your “this query takes 20s instead of 20ms to run” degrades to the even more annoying “well, sometimes it’s 5s, and sometimes 40s”. &lt;strong>¹&lt;/strong> In MySQL, we work with 16KB block size, and in indexes we usually have a fan out of a few hundreds to one thousand per block or tree level. The depth of the index tree is the number of comparisons, and it is log to the base of (fan out) of the table length in records. This then becomes ln(table length)/ln(fan out), because that is how you get arbitrary base logs from ln(). For a fan out of 100, we get a depth of 3 for 1 million, and 4.5 for 1 billion records. For a fan out of 1000, it’s 2 for the million, and 3 for the billion. Plus one for the actual record, so the magical database number is 4: It’s always 4 media accesses to get any record through a tree index - stable execution times for indexed queries, because math works.&lt;/p>
&lt;h2 id="where-monitoring-ends-and-observability-begins">Where Monitoring ends and Observability begins&lt;/h2>
&lt;p>With measurements, aggregations, and the visualisation as a heatmap, I can identify my outliers – that is, I learn that I have them and where they are in time and maybe space (group of hosts). But with a common monitoring agents such as Diamond or Telegraf, what is being recorded are numbers or even aggregates of numbers - the quantisation into time and value buckets happens in the client and all that is recorded in monitoring is “there have been 4 queries of 4-8ms run time at 17:10:20 on host randomdb-2029”. We don’t know what queries they were, where they came from or whatever other context may be helpful. With events, we optionally get rich records for each query - query text, stack trace context, runtime, hostname, database pool name and many other pieces of information. They are being aggregated as they come in, or can be aggregated along other, exotic dimensions after the fact. And best of all, once we find an outlier, we can go back from the outlier and find all the events that are within the boundary conditions of the section of the heapmap that we have marked up as an outlier. This also is the fundamental difference between monitoring (“We know we had an abnormal condition in this section of time and space”) and observability (“… and these are the events that make up the abnormality, and from them we can see why and how things went wrong.”). (Written after a longer call with a colleague on this subject).&lt;/p>
&lt;p>&lt;em>First published on &lt;a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">https://blog.koehntopp.info/&lt;/a> and syndicated here with permission of the author.&lt;/em>&lt;/p></content:encoded><author>Kristian Köhntopp</author><category>Honeycomb</category><category>Monitoring</category><category>MySQL</category><category>Open Source Databases</category><category>SolarWinds</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/11/obs-no_hu_14a32059177eeed0.jpg"/><media:content url="https://percona.community/blog/2020/11/obs-no_hu_414f3a22a9e300c7.jpg" medium="image"/></item><item><title>How to build a high-performance application on Tarantool from scratch</title><link>https://percona.community/blog/2020/10/30/how-to-build-a-high-performance-application-on-tarantool-from-scratch/</link><guid>https://percona.community/blog/2020/10/30/how-to-build-a-high-performance-application-on-tarantool-from-scratch/</guid><pubDate>Fri, 30 Oct 2020 14:44:18 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="1280" height="664" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/10/image1_hu_193ea30e9fcb8416.webp 768w, https://percona.community/blog/2020/10/image1_hu_32bb609a6fc1b235.webp 480w, https://percona.community/blog/2020/10/image1_hu_440d7163475ca62f.webp 1280w" src="https://percona.community/blog/2020/10/image1_hu_440d7163475ca62f.webp" alt="tarantool start" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I came to Mail.ru Group in 2013, and I required a queue for one task. First of all, I decided to check what the company had already got. They told me they had this Tarantool product, and I checked how it worked and decided that adding a queue broker to it could work perfectly well. I contacted Kostja Osipov, the senior expert in Tarantool, and the next day he gave me a 250-string &lt;a href="https://github.com/mailru/tntlua/commit/f879dfb6981dc82287b7243074ca6cc9c6038369" target="_blank" rel="noopener noreferrer">script&lt;/a> that was capable of managing almost everything I needed. Since that moment, I have been in love with Tarantool. It turned out that a small amount of code written with a quite simple script language was capable of ensuring some totally new performance for this DBMS. Today, I’m going to tell you how to instantiate your own queue in Tarantool 2.2. At that moment, I enjoyed a simple and fast queue broker Beanstalkd. It offered a user-friendly interface, task status tracking by connection (client’s disconnection returned the task into the queue), as well as practical opportunities for dealing with delayed tasks. I wanted to make something like that. Here is how the service works: there is a queue broker that accepts and stores tasks; there are clients: producers sending tasks (put method); and consumers taking tasks up (take method).
&lt;figure>&lt;img width="632" height="337" sizes="(max-width: 632px) 100vw, 632px" srcset="https://percona.community/blog/2020/10/image3-1_hu_9c616720bb6d8d8b.webp 480w, https://percona.community/blog/2020/10/image3-1_hu_cbca01c7807d41e0.webp 632w" src="https://percona.community/blog/2020/10/image3-1_hu_cbca01c7807d41e0.webp" alt="tarantool 1" loading="lazy" />&lt;/figure> This is how one task’s lifecycle looks like. The task is sent with the put method and then goes to the ready state. The take operation changes the task’s status to taken. The taken task can be acknowledged (ack) and removed or changed back to ready (release-d).
&lt;figure>&lt;img width="308" height="210" sizes="(max-width: 308px) 100vw, 308px" srcset="https://percona.community/blog/2020/10/image2-1_hu_f720b97d6a1f2c2d.webp 308w" src="https://percona.community/blog/2020/10/image2-1_hu_f720b97d6a1f2c2d.webp" alt="tarantool 2" loading="lazy" />&lt;/figure> Procession of delayed tasks can be added:
&lt;figure>&lt;img width="428" height="318" sizes="(max-width: 428px) 100vw, 428px" srcset="https://percona.community/blog/2020/10/image5_hu_cd983c3024f1b77d.webp 428w" src="https://percona.community/blog/2020/10/image5_hu_cd983c3024f1b77d.webp" alt="tarantool 3" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="neighborhood-setup">&lt;strong>Neighborhood setup&lt;/strong>&lt;/h2>
&lt;p>Today, Tarantool is also a LuaJIT interpreter. To start working with it, an entry point is required – an initial file init.lua. After that a box.cfg()shall be called – it starts the DBMS internals. For local development, you only have to connect and start the console. Then create and run a file as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">os.exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The console is interactive and can be instantly put to use. There is no need to install and/or set up multiple tools and learn to use them. It only takes to write 10 to 15 strings of code on any local machine. Another advice from me is to use the strict mode. Lua language is quite easy on the variable declaration, and this mode to a certain extent will help you with error management. If you build Tarantool on your own in the DEBUG mode, the strict mode will be on by default. Let’s run our file with Tarantool:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool init.lua&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You’ll see something like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.344 [30043] main/102/init.lua C> Tarantool 2.2.3-1-g98ecc909a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.345 [30043] main/102/init.lua C> log level 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.346 [30043] main/102/init.lua I> mapping 268435456 bytes for memtx tuple arena...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.347 [30043] main/102/init.lua I> mapping 134217728 bytes for vinyl tuple arena...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.370 [30043] main/102/init.lua I> instance uuid 38c59892-263e-42de-875c-8f67539191a3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.371 [30043] main/102/init.lua I> initializing an empty data directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.408 [30043] main/102/init.lua I> assigned id 1 to replica 38c59892-263e-42de-875c-8f67539191a3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.408 [30043] main/102/init.lua I> cluster uuid 7723bdf4-24e8-4957-bd6c-6ab502a1911c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.425 [30043] snapshot/101/main I> saving snapshot `./00000000000000000000.snap.inprogress'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.437 [30043] snapshot/101/main I> done
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.439 [30043] main/102/init.lua I> ready to accept requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:00:11.439 [30043] main/104/checkpoint_daemon I> scheduled next checkpoint for Thu Jul 9 21:11:59 2020
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="writing-a-queue">&lt;strong>Writing a queue&lt;/strong>&lt;/h2>
&lt;p>Let’s create a file queue.lua to write our app. We can add all of it right to init.lua, but working with an independent file is handier. Now, connect the queue as a module from the init.lua file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue = require 'queue'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">os.exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>All the following modifications will be made in queue.lua. As we’re making a queue, we need a place to store the task data. Let’s create a space – a data table. It can be made optionless, but we’re going to add something at once. For regular restart we have to indicate that a space shall be created only in case it doesn’t exist (if_not_exists). Another thing – in Tarantool, you can indicate the field format with content description (and it is a good idea to do so). I’m going to take a very simple structure for the queue. I’ll need only task id-s, their statuses, and some random data. Data can’t be used with a primary index, so we create an index in accordance with id. Make sure the field type of the format and the index match.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.schema.create_space('queue',{ if_not_exists = true; })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:format( {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'id'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'status'; type = 'string' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'data'; type = '*' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('primary', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { 1,'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then, we make a global queue table that will contain our functions, attributes, and methods. First of all, we bring out two functions: putting a task (put) and taking a task (take). The queue will show states of the tasks. For status indication, we’ll make another table. Numbers or strings can be used as values, but I like one-symbol references – they can be semantically relevant, and they take little place to be stored. First of all, we create two statuses: R=READY and T=TAKEN.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local queue = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local STATUS = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.READY = 'R'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.TAKEN = 'T'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.put(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">return queue&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>How do we make put? Easy as pie. We need to generate an id and insert the data to a space with the READY status. There are many ways to generate an indicator, but we’ll take clock.realtime. It can automatically determine the message queue. However, remember that the clock is likely to readjust, causing a wrong message order. Another thing is that a task with the same value can appear in the queue. You can check if there is a task with the same id, and in case of collision you just one unit. This takes microseconds, and this situation is highly unlikely, so efficiency won’t be affected. All the arguments of the function shall be added to our task:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local clock = require 'clock'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local new_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repeat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new_id = clock.realtime64()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> until not box.space.queue:get(new_id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return new_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.put(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local id = gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:insert{ id, STATUS.READY, { ... } }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After we’ve written the put function, we can restart Tarantool and call this function instantly. The task will be added to the queue, now looking like a tuple. We can add random data and even nested structures to it. Tuples that Tarantool uses to store data are packed into the MessagePack, which facilitates storing of these structures.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> queue.put("hello")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594325382148311477, 'R', ['hello']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.put("my","data",1,2,3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594325394527830491, 'R', ['my', 'data', 1, 2, 3]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.put({ complex = { struct = "data" }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594325413166109943, 'R', [{'complex': {'struct': 'data'}}]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Everything we put remains within the space. We can take space commands to see what we have there:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> box.space.queue:select()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- - [1594325382148311477, 'R', ['hello']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [1594325394527830491, 'R', ['my', 'data', 1, 2, 3]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [1594325413166109943, 'R', [{'complex': {'struct': 'data'}}]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, we need to learn how to take tasks. For this, we make a take function. We take the tasks that are ready for processing, i. e., the ones with the READY status. We can check the primary key and find the first ready task, but if there’re a lot of tasks to be processed this scenario won’t work. We’ll need a special index using the status field. One of the main differences between Tarantool and the key-value databases is that the former facilitates the creation of diverse indexes, almost like in relational databases: using various fields, composite ones, of different kinds. Then, we create the second index, indicating that the first field shows status. This will be our search option. The second field is id. It will put the tasks with the same status in the ascending order.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('status', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { 2, 'string', 1, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s take predefined functions for our selection. There’s a special iterator that is applied to a space as pairs. We pass a part of the key to it. Here, we have to deal with a composite index, which contains two fields. We use the first one for searching and the second one for putting things in order. We command the system to find the tuples that match the READY status in the first part of their index. And the system will present them put in order in accordance with the second part of the index. If we find anything, we’ll take that task, update it and return it. An update is required to prevent anybody with the same take call taking it. If there are no tasks, we return nil.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.take()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', 2, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Please, note that the first tuple level in Tarantool is an array. It has no names, but only numbers, and that’s why the field number used to be required at operations like update. Let’s make an auxiliary element – a table, to match the field names and numbers. To compile such a table we can use the format we’ve already written:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local F = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for no,def in pairs(box.space.queue:format()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[no] = def.name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[def.name] = no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For better visibility, we can correct descriptions of indexes like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space.queue:format( {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'id'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'status'; type = 'string' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'data'; type = '*' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local F = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for no,def in pairs(box.space.queue:format()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[no] = def.name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[def.name] = no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('primary', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('status', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.status, 'string', F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we can implement take in whole:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.take(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({ STATUS.READY },{ iterator='EQ' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:update({t.id},{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { '=', F.status, STATUS.TAKEN }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s check how it works. For this, we’ll put one task and call take twice. If by that moment we have any data in the space, we can clear it with the command box.space.queue:truncate():&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> queue.put("my","data",1,2,3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594325927025602515, 'R', ['my', 'data', 1, 2, 3]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.take()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594325927025602515, 'T', ['my', 'data', 1, 2, 3]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.take()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The first take will return us the task we’ve put. As soon as we call take for the second time, nil is returned, because there are no more ready-tasks (with R status). To make sure, we run a select command from the space:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> box.space.queue:select()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- - [1594325927025602515, 'T', ['my', 'data', 1, 2, 3]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The consumer taking the task shall either acknowledge its procession or release it without a procession. In the latter case, somebody else will be able to take the task. For this, two functions are used: ack and release. They receive the task’s id and look for it. If the task’s status shows it’s been taken, we process it. These functions are really similar: one removes processed tasks, and the other returns them with a ready status.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.ack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = assert(box.space.queue:get{id},"Task not exists")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t and t.status == STATUS.TAKEN then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:delete{t.id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error("Task not taken")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = assert(box.space.queue:get{id},"Task not exists")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t and t.status == STATUS.TAKEN then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error("Task not taken")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s see how it works with all four functions. We will put two tasks and take the first of them, then releasing them. It returns to the R status. The second take call takes the same task. If we process it, it will be removed. The third take call will take the second task. The order will be observed. In case the task has been taken, it won’t be available for anybody else.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> queue.put("task 1")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326185712343931, 'R', ['task 1']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.put("task 2")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326187061434882, 'R', ['task 2']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> task = queue.take() return task
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326185712343931, 'T', ['task 1']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.release(task.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326185712343931, 'R', ['task 1']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> task = queue.take() return task
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326185712343931, 'T', ['task 1']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.ack(task.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326185712343931, 'T', ['task 1']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> task = queue.take() return task
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326187061434882, 'T', ['task 2']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.ack(task.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326187061434882, 'T', ['task 2']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> task = queue.take() return task
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is a properly working queue. We are already capable of writing a consumer to process the tasks. However, there is a problem. When we call take, the function instantly returns either a task or an empty string. If we write a cycle for task procession and start it, it will run unproductively, doing nothing and simply wasting the CPU.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local task = queue.take()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if task then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -- ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To fix this, we’ll need a primitive channel. It enables message communication. In fact, it’s a FIFO queue for fiber communication. We have a fiber that puts the tasks when we access the database through the network or via the console. At the fiber, our Lua-code is executed, and it needs some primitive to inform the other fiber awaiting the task that there is a new one available. This is how a channel works: it can contain a buffer with N slots where a message can be located, even if no one is checking the channel. Another option is creating a channel without a buffer: this way, messages will be only acceptable in the slots that somebody waiting for. Let’s say, we create a channel for two buffer elements. It has two slots for put. If one consumer is waiting at the channel, it will create a third slot for put. If we are going to send messages via this channel, three put operations will be enabled without blocking, but the fourth put operation will be blocked by the fiber that sends messages via this channel. This is how an inter-fiber communication is set up. If for any chance you are familiar with channels in Go, they are literally the same there:
&lt;figure>&lt;img width="780" height="276" sizes="(max-width: 780px) 100vw, 780px" srcset="https://percona.community/blog/2020/10/image4-1_hu_7671bec13cf5b9f1.webp 768w, https://percona.community/blog/2020/10/image4-1_hu_304fb668a16b2623.webp 480w, https://percona.community/blog/2020/10/image4-1_hu_f790eb35ea6f56c9.webp 780w" src="https://percona.community/blog/2020/10/image4-1_hu_f790eb35ea6f56c9.webp" alt="tarantool 4" loading="lazy" />&lt;/figure> Let’s slightly modify the take function. First of all, we add a new argument – timeout, implying we’re ready to wait for the task within a set period of time. We make a cycle to search for a ready task. If it can’t be found, the cycle will compute how long it has to wait. Now, let’s make a channel that will wait along with this timeout. While the fiber is pending at the channel (asleep), it can be woken up externally by sending a message via the channel.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._wait = fiber.channel()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not timeout then timeout = 0 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while not found do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local left = (now + timeout) - fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if left &lt;= 0 then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:get(left)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Altogether, take tries to take the task and if this is managed, the task is returned. However, if there is no task, it can be awaited for the rest of the timeout. Besides, the other party that creates the task will be able to wake this fiber up. To make the performance of various tests more convenient, we can globally connect the fiber module in the init.lua file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">fiber = require 'fiber'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s see how this works without waking the fiber up. In an independent fiber, we’ll put a task with a 0.1 sec. delay, i. e. at first the queue will be empty, and the task will appear in 0.1 sec. after starting. Upon that, we’ll set up a 3 sec. timeout for the take call. After the start, take will try to find the task, and then if there’s none, it goes to sleep for 3 sec. In 3 sec. it wakes up, searches again, and finds the task.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:truncate()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.create(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.put("task 3")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local start = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return queue.take(3), { wait = fiber.time() - start }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594326905489650533, 'T', ['task 3']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- wait: 3.0017817020416
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, let’s make take wake up at the tasks’ appearance. For this, we’ll take our old put function and update it with a message sent via the channel. The message can be literally anything. Let it be true here. Previously, I demonstrated that put can be blocked if the channel lacks place. At the same time, the task producer doesn’t care if there are consumers on the other side. It shouldn’t get blocked while waiting for a consumer. So, it’s a reasonable thing to set up a zero timeout for blocking here. If there are some consumers out there, i. e., the ones who need to be messaged about the new task, we’ll wake them up. Otherwise, we won’t be able to send the message via the channel. An alternative option is to check if the channel has any active readers.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.put(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local id = gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:put(true,0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:insert{ id, STATUS.READY, { ... } }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now the take code is going to work in a totally different way. We create a task in 0.1 sec. and take instantly wakes up and receives it. We’ve got rid of the hot cycle that has been continuously pending, awaiting tasks. If we don’t put a task, the fiber will wait for 3 seconds.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:truncate()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.create(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.put("task 4")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local start = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return queue.take(3), { wait = fiber.time() - start }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- [1594327004302379957, 'T', ['task 4']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- wait: 0.10164666175842
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’ve tested how things work within the instance, and now let’s try some networking. First of all, let’s create a server. For that, we add the listen option to box.cfg of our init.lua file (it will be a port used for listening). At the same time, we’ll need to give permissions. Right now we’re not going to study privilege setting up in detail, but let’s make every connection have an execution privilege. To read about the rights please check &lt;a href="https://www.tarantool.io/en/doc/latest/book/box/authentication/" target="_blank" rel="noopener noreferrer">this&lt;/a>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen = '127.0.0.1:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue = require 'queue'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">os.exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s create a producer client for task generation. Tarantool already has a module that facilitates connection to another Tarantool.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#!/usr/bin/env tarantool
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if #arg &lt; 1 then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error("Need arguments",0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local netbox = require 'net.box'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local conn = netbox.connect('127.0.0.1:3301')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local yaml = require 'yaml'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local res = conn:call('queue.put',{unpack(arg)})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(yaml.encode(res))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">conn:close()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tarantool producer.lua "hi"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- [1594327270675788959, 'R', ['hi']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The consumer will connect, call take with a timeout, and process the result. If it receives the task, we’ll print or release it but won’t process it yet. Let’s say, we’ve received the task.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#!/usr/bin/env tarantool
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local netbox = require 'net.box'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local conn = netbox.connect('127.0.0.1:3301')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local yaml = require 'yaml'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local task = conn:call('queue.take', { 1 })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if task then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> print("Got task: ", yaml.encode(task))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> conn:call('queue.release', { task.id })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> print "No more tasks"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But when we try to release the task, something odd happens:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ tarantool consumer.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Got task:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- [1594327270675788959, 'T', ['hi']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ER_EXACT_MATCH: Invalid key part count in an exact match (expected 1, got 0)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s delve into this matter. When the consumer will once again attempt to execute the task we’ll see that at the previous start it has taken the task but hasn’t been able to return it. Some error’s occurred, and the tasks got stuck. Such tasks become unavailable for other consumers, and there is nobody to return them to, as the code used to take them has been completed.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ tarantool consumer.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No more tasks
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No more tasks
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">```select shows that the tasks have been taken.```
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> box.space.queue:select()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- - [1594327004302379957, 'T', ['task 3']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [1594327270675788959, 'T', ['hi']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We have several issues at once here. Let’s start with the automatic release of the tasks in case the client disconnects. Tarantool contains triggers for client connection and disconnection. If we add them, we’ll be able to learn about connection and disconnection events.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local log = require 'log'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.session.on_connect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "connected %s from %s", box.session.id(), box.session.peer() )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.session.on_disconnect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "disconnected %s from %s", box.session.id(), box.session.peer() )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:52:09.107 [32604] main/115/main I> connected 2 from 127.0.0.1:36652
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:52:10.260 [32604] main/116/main I> disconnected 2 from nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:52:10.823 [32604] main/116/main I> connected 3 from 127.0.0.1:36654
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-07-09 20:52:11.541 [32604] main/115/main I> disconnected 3 from nil&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>There is this term, session id, and we can check the IP address used to connect, as well as the time of disconnection. However, calling session.peer()actually calls getpeername(2) right over the socket. That’s why at disconnection we don’t see who’s disconnected (as getpeername is called over a closed socket). Let’s do some minor hacking, then. Tarantool has a box.session.storage — a temporary table, to which anything you wish can be saved during the session lifetime. At connection, we can keep in mind the ones connected to know who’s disconnected. This will make adjustments easier.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.session.on_connect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.peer = box.session.peer()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "connected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.session.on_disconnect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "disconnected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So, we have a client disconnection event. And we need to somehow release the tasks it has taken. Let’s introduce the term “possession of the task.” The session that has taken the task ought to answer for it. Let’s make two tables to save these data and modify the take function:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">queue.taken = {}; -- list of tasks taken
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue.bysid = {}; -- list of tasks for the specific session
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not timeout then timeout = 0 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while not found do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local left = (now + timeout) - fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if left &lt;= 0 then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:get(left)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Register %s by %s", found.id, sid)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ found.id ] = sid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = queue.bysid[ sid ] or {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ][ found.id ] = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’ll use this table to memorize that a certain task has been taken by a certain session. We’ll also need to modify the task returning code, ack, and release. Let’s make a single common function to check if the task is there and if it has been taken by a specific session. Then, it will be impossible to take the task under one connection and then return under another one requesting its deletion due to its procession completion.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-32">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local function get_task( id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not id then error("Task id required", 2) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get{id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task {%s} was not found", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not queue.taken[id] then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s not taken by anybody", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue.taken[id] ~= box.session.id() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s taken by %d. Not you (%d)",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id, queue.taken[id], box.session.id() ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now ack and release functions become very simple. We use them to call get_task, which checks if the task is possessed by us and if it is taken. Then we can work with it.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-33">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.ack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ t.id ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ t.id ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:delete{t.id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ t.id ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ t.id ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To reset statuses of all the tasks to R SQL or Lua snippet can be used:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-34" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-34">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.execute[[ update "queue" set "status" = 'R' where "status" = 'T' ]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue.index.status:pairs({'T'}):each(function(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:update({t.id},{{'=',2,'R'}}) end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When we call the consumer again, it replies: task ID required.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-35" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-35">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ tarantool consumer.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Got task:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- [1594327004302379957, 'T', ['task 3']]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ER_PROC_LUA: queue.lua:113: Task id required&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Thus, we’ve found the first problem in our code. When we work in Tarantool, a tuple is always associated with the space. The latter has a format, and the format has field names. That’s why we can use field names in a tuple. When we take it beyond the database, a tuple becomes just an array with a number of fields. If we refine the format of return from the function, we’ll be able to return not tuples, but objects with names. For this, we’ll apply the method :tomap{ names_only = true }:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-36" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-36">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.put(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :insert{ id, STATUS.READY, { ... } }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.ack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:delete{t.id}:tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">return queue&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Having replaced it, we’ll encounter another issue.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-37" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-37">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ tarantool consumer.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Got task:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- {'status': 'T', 'data': ['hi'], 'id': 1594327270675788959}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ER_PROC_LUA: queue.lua:117: Task 1594327270675788959ULL not taken by anybody&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we try to release the task the system will answer that we haven’t taken it. Moreover, we will see the same ID, but with a suffix – ULL. Here we encounter a trick of the LuaJIT extention: FFI (Foreign Function Interface). Let’s delve into this matter. We add five values to the table using various alternatives of numeral 1 designation as the keys.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-38" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-38">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> t = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t[1] = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t["1"] = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t[1LL] = 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t[1ULL] = 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t[1ULL] = 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 1: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> '1': 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We would expect them to be displayed as 2 (string + number) or 3 (string + number + LL). But when displayed, all the keys will appear in the table separately: we will still see all the values – 1, 2, 3, 4, 5. Moreover, at serialization, we won’t see any difference between regular, signed, or unsigned numbers.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-39" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-39">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> return t[1], t['1'], t[1LL], t[1ULL]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>However, the most amusing thing happens when we try to extract data from the table. It goes well with regular Lua-types (number and string), but it doesn’t with LL (long long) and ULL (unsigned long long). They are a separate type of cdata, intended for working with C language types. When saving into a Lua table, cdata is hashed by address, not by value. Two numbers, no matter if they are the same in value, simply have different addresses. And when we add ULL to the table, we can’t extract them using the same value. That’s why we’ll have to change our queue and key possession a bit. This is a forced move, but it will enable random modification of our keys in the future. Somehow, we need to transform our key into a string or a number. Let’s take the MessagePack. In Tarantool, it’s used to store tuples, and it will pack our values just like Tarantool itself does. With this pack, we’ll transform a random key into a string that will become a key to our table.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-40" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-40">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local msgpack = require 'msgpack'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function keypack( key )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return msgpack.encode( key )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function keyunpack( data )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return msgpack.decode( data )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then we add the key package to take and save it into the table. In the function get_task we need to check if the key has passed in a correct format, and if not, we change it to int64. After that, we use keypack to pack the key to the MessagePack. As this packed key will be required by all the functions that use it, we’ll return it from get_task, so that ack and release could use it and clean it out from the sessions.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-41" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-41">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not timeout then timeout = 0 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while not found do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local left = (now + timeout) - fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if left &lt;= 0 then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:get(left)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Register %s by %s", found.id, sid)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local key = keypack( found.id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = sid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = queue.bysid[ sid ] or {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ][ key ] = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function get_task( id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not id then error("Task id required", 2) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id = tonumber64(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local key = keypack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get{id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task {%s} was not found", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not queue.taken[key] then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s not taken by anybody", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue.taken[key] ~= box.session.id() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s taken by %d. Not you (%d)",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id, queue.taken[key], box.session.id() ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return t, key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.ack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t, key = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:delete{t.id}:tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t, key = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we have a disconnection trigger, we know that a certain session has disconnected – the one possessing certain keys. We can take all the keys from that session and automatically return them to their initial state — ready. Besides, this session may contain some tasks awaiting to be take-n. Let’s mark them in the session.storage for the tasks not to be taken.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-42" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-42">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.session.on_disconnect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "disconnected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.destroyed = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local bysid = queue.bysid[ sid ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if bysid then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while next(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for key, id in pairs(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autorelease %s by disconnect", id);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bysid[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not timeout then timeout = 0 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while not found do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local left = (now + timeout) - fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if left &lt;= 0 then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:get(left)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if box.session.storage.destroyed then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Register %s by %s", found.id, sid)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local key = keypack( found.id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = sid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = queue.bysid[ sid ] or {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ][ key ] = found.id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>For testing purposes, tasks can be taken as a group:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-43" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-43">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantoolctl connect 127.0.0.1:3301 &lt;&lt;&lt; 'queue.take()'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>At adjustment, you might see that you’ve taken the tasks, thus dropping the queue, but at restart the tasks aren’t possessed by anybody (because all connections were interrupted when you switched off), but they get the taken status. That’s why we’ll update our code with status modification at startup. Thus, the database will be started, releasing all the tasks taken.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-44" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-44">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue.index.status:pairs({STATUS.TAKEN}):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then break end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autoreleased %s at start", t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we have a queue ready for operation.&lt;/p>
&lt;h2 id="adding-delayed-procession">&lt;strong>Adding delayed procession&lt;/strong>&lt;/h2>
&lt;p>Thereat, we only have to add delayed tasks. For that, let’s add a new field and a relevant index. We’re going to use this field to store the time when a certain task’s state should be changed. To this end, we’re modifying the put function and adding a new status: W=WAITING.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-45" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-45">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space.queue:format( {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'id'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'status'; type = 'string' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'runat'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'data'; type = '*' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('runat', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.runat, 'number', F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.WAITING = 'W'&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As we are flip-flopping the pattern, and as this is a development mode, let’s clear the previous pattern (via the console):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-46" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-46">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space.queue.drop()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.snapshot()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, let’s restart our queue. Then, we add support of delay in put and release. If delay is passed on, the task’s status shall be changed to WAITING, and we have to define when it is subject to the procession. Another thing we need is a processor. For this, we can use background fibers. At any moment we can create a fiber that isn’t associated with any connections and works in the background. Let’s make a fiber that will work infinitely and await the nearest tasks.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-47" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-47">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.put(data, opts)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local id = gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local runat = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local status = STATUS.READY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if opts and opts.delay then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runat = clock.realtime() + tonumber(opts.delay)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status = STATUS.WAITING
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:put(true,0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :insert{ id, status, runat, data }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only=true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id, opts)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t, key = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local runat = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local status = STATUS.READY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if opts and opts.delay then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runat = clock.realtime() + tonumber(opts.delay)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status = STATUS.WAITING
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update({t.id},{{ '=', F.status, status },{ '=', F.runat, runat }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If a time comes for some of the tasks, we modify it, changing its status from waiting to ready and also notifying the clients that might be awaiting a task. Now, we put a delayed task. Call take, make sure that there are no ready tasks. Call it again with a timeout, which fits into the task’s appearance. As soon as it appears, we see it’s been done by the fiber queue.runat.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-48" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-48">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">queue._runat = fiber.create(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.runat')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remaining
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = clock.realtime()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in box.space.queue.index.runat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs( { 0 }, { iterator = 'GT' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.runat > now then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> remaining = t.runat - now
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.status == STATUS.WAITING then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Runat: W->R %s",t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {'=', F.status, STATUS.READY },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {'=', F.runat, 0 },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Runat: bad status %s for %s", t.status, t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id },{{ '=', F.runat, 0 }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remaining or remaining > 1 then remaining = 1 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(remaining)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="monitoring">&lt;strong>Monitoring&lt;/strong>&lt;/h2>
&lt;p>Never forget about monitoring the queue, because it can extend too much or even run out. We can count the number of tasks with every status in the queue and start sending the data to monitoring.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-49" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-49">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function queue.stats()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total = box.space.queue:len(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ready = box.space.queue.index.status:count({STATUS.READY}),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> waiting = box.space.queue.index.status:count({STATUS.WAITING}),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> taken = box.space.queue.index.status:count({STATUS.TAKEN}),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> queue.stats()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ready: 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> taken: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> waiting: 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total: 17
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> local clock = require 'clock' local s = clock.time() local r = queue.stats() return r, clock.time() - s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ready: 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> taken: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> waiting: 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total: 17
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 0.00057339668273926
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Such monitoring will work quite fast as long as there are not too many tasks. The normal state of the queue is empty. But suppose we have a million tasks. Our stats function still shows the correct value but works rather slowly. The issue is caused by the index:count call — this is always a full scan by index. Let’s cash the values of the counters.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-50" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-50">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">queue._stats = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for k,v in pairs(STATUS) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[v] = 0LL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for _,t in box.space.queue:pairs() do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ t[F.status] ] = (queue._stats[ t[F.status] ] or 0LL)+1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.stats()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total = box.space.queue:len(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ready = queue._stats[ STATUS.READY ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> waiting = queue._stats[ STATUS.WAITING ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> taken = queue._stats[ STATUS.TAKEN ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, this function will work very fast regardless of the number of records. We only have to update the counters at any operations. Prior to every operation, we have to reduce one value and increase the other. We also can manually set updates of the functions, but errors and contradictions are possible. Luckily, Tarantool has triggers for spaces that are capable of tracing any changes in the space. You can even manually execute space:update or space:delete – the trigger will take that into account, too. The trigger will account for all the statuses according to the value used in the database. At the restart, we’ll once account for the values of all the counters.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-51" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-51">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space.queue:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if old then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ old[ F.status ] ] = queue._stats[ old[ F.status ] ] - 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ new[ F.status ] ] = queue._stats[ new[ F.status ] ] + 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>There is one more operation that can’t be traced in the space directly but affects its content: space:truncate(). To monitor the clearing of the space a special space trigger can be used — _truncate.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-52" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-52">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">box.space._truncate:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new.id == box.space.queue.id then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue._stats) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[k] = 0LL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After that everything will work accurately and consistently. Statistics can be sent over the network. Tarantool has convenient non-blocking sockets that can be used rather low-level, almost like in C. To demonstrate how it works let send the metrics in a Graphite format using UDP:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-53" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-53">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local socket = require 'socket'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local errno = require 'errno'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_host = '127.0.0.1'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_port = 2003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local ai = socket.getaddrinfo(graphite_host, graphite_port, 1, { type = 'SOCK_DGRAM' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local addr,port
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for _,info in pairs(ai) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> addr,port = info.host,info.port
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if not addr then error("Failed to resolve host") end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._monitor = fiber.create(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.monitor')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.yield()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remote = socket('AF_INET', 'SOCK_DGRAM', 'udp')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue.stats()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local msg = string.format("queue.stats.%s %s %sn", k, tonumber(v), math.floor(fiber.time()))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local res = remote:sendto(addr, port, msg)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not res then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Failed to send: %s", errno.strerror(errno()))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>or using TCP:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-54" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-54">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local socket = require 'socket'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local errno = require 'errno'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_host = '127.0.0.1'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_port = 2003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._monitor = fiber.create(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.monitor')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.yield()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remote = require 'socket'.tcp_connect(graphite_host, graphite_port)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remote then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Failed to connect to graphite %s",errno.strerror())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local data = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue.stats()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> table.insert(data,string.format("queue.stats.%s %s %sn",k,tonumber(v),math.floor(fiber.time())))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> data = table.concat(data,'')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remote:send(data) then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("%s",errno.strerror())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="hot-code-reloading">&lt;strong>Hot code reloading&lt;/strong>&lt;/h2>
&lt;p>An important feature of the Tarantool platform is hot code reloading. It is rarely required in regular apps, but when you have Gigabytes of data stored in the database and every reload takes time, hot reloading is quite helpful.  When Lua loads some code on require, the content of the file is interpreted, and the returned result is cashed in the system table package.loaded under the module’s name. Subsequent require calls of the same module won’t read the file again but will return its cached value. To make Lua reinterpret and redownload the file you just have to delete the relevant record from package.loaded[…] and call require again. You need to memorize what the runtime has preloaded because there won’t be files for reloading of inbuilt modules. The simplest code snippet for reload procession looks like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-55" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-55">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen = '127.0.0.1:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local not_first_run = rawget(_G,'_NOT_FIRST_RUN')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">_NOT_FIRST_RUN = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if not_first_run then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(package.loaded) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not preloaded[k] then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> package.loaded[k] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> preloaded = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(package.loaded) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> preloaded[k] = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue = require 'queue'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">os.exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As the code reload is a typical and regular task, we already have a set module &lt;a href="https://github.com/moonlibs/package-reload" target="_blank" rel="noopener noreferrer">package.reload&lt;/a>, which we use in most of the apps. It memorizes the file used to download data, the modules that were preloaded, and then provides a convenient call for reload initiation: package.reload().&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-56" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-56">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen = '127.0.0.1:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require 'package.reload'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue = require 'queue'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">os.exit()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To make the code reloadable, you should write it in a slightly different way. Mind that the code can be executed repeatedly. At first, it is executed at the first start, and subsequently, it is executed at reloading. We have to clearly process this situation.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-57" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-57">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local queue = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local old = rawget(_G,'queue')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if old then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken = old.taken
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid = old.bysid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._triggers = old._triggers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats = old._stats
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait = old._wait
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch = old._runch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runat = old._runat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._triggers = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait = fiber.channel()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch = fiber.cond()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue.index.status:pairs({STATUS.TAKEN}):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then break end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autoreleased %s at start", t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(STATUS) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[v] = 0LL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in box.space.queue:pairs() do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ t[F.status] ] = (queue._stats[ t[F.status] ] or 0LL)+1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Perform initial stat counts %s", box.tuple.new{ queue._stats })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Besides, you need to remember about trigger reloading. If you leave the issue as is, every reload will cause the installation of an additional trigger. However, triggers support an indication of the old function, so the installation of the trigger returns it. That’s why we’ll just save the installation result to a variable and pass it on as an argument. At the first start, there will be no variable, and a new trigger will be installed. However, at subsequent loading, the trigger will be replaced.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-58" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-58">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">queue._triggers.on_replace = box.space.queue:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if old then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ old[ F.status ] ] = queue._stats[ old[ F.status ] ] - 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ new[ F.status ] ] = queue._stats[ new[ F.status ] ] + 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_replace)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_truncate = box.space._truncate:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new.id == box.space.queue.id then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue._stats) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[k] = 0LL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_truncate)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_connect = box.session.on_connect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.peer = box.session.peer()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "connected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_connect)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_disconnect = box.session.on_disconnect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "disconnected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.destroyed = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local bysid = queue.bysid[ sid ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if bysid then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while next(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for key, id in pairs(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autorelease %s by disconnect", id);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bysid[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_disconnect)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Another essential element at reloading is fibers. A fiber is started in the background, and we don’t control it in any way. It has while … true written, and it never stops and doesn’t reload on its own. To communicate with it we’ll need a channel or rather a fiber.cond: condition variable. There are several different approaches to fiber reload. For example, the old ones can be deleted with the fiber.kill call, but this is not a very consistent take on the issue, as we may call kill at the wrong time. This is why we usually use the fiber generation attribute: the fiber proceeds working only in the generation it has been created. At code reload, the generation changes and the fiber clearly ends. Moreover, we can prevent the simultaneous operation of several fibers, checking the status of the previous generation fiber.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-59" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-59">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">queue._runat = fiber.create(function(queue, gen, old_fiber)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.runat.'..gen)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen and old_fiber and old_fiber:status() ~= 'dead' do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Waiting for old to die")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:wait(0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Started...")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remaining
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = clock.realtime()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in box.space.queue.index.runat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs( {0}, { iterator = 'GT' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.runat > now then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> remaining = t.runat - now
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.status == STATUS.WAITING then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Runat: W->R %s",t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { '=', F.status, STATUS.READY },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { '=', F.runat, 0 },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Runat: bad status %s for %s", t.status, t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id },{{ '=', F.runat, 0 }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remaining or remaining > 1 then remaining = 1 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:wait(remaining)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:broadcast()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Finished")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue, package.reload.count, queue._runat)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._runch:broadcast()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And in the end, at code reload you get an error saying the console is already on. This is how this situation can be dealt with:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-60" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-60">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">if not fiber.self().storage.console then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> os.exit()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="lets-summarize">&lt;strong>Let’s summarize&lt;/strong>&lt;/h2>
&lt;p>We’ve written a working network queue with delayed processing, automatic task return by means of triggers, statistics forwarding in Graphite using TCP, and explored quite a few issues. With average state-of-the-art hardware, such a queue will easily support the transmission of 20+ thousand messages per second. It contains about 300 code strings and can be compiled in a day, document studies included. Final files: &lt;strong>queue.lua:&lt;/strong>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-61" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-61">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local clock = require 'clock'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local errno = require 'errno'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local log = require 'log'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local msgpack = require 'msgpack'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local socket = require 'socket'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.schema.create_space('queue',{ if_not_exists = true; })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:format( {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'id'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'status'; type = 'string' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'runat'; type = 'number' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { name = 'data'; type = '*' },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} );
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local F = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for no,def in pairs(box.space.queue:format()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[no] = def.name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> F[def.name] = no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('primary', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('status', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.status, 'string', F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.space.queue:create_index('runat', {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> parts = { F.runat, 'number', F.id, 'number' };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if_not_exists = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local STATUS = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.READY = 'R'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.TAKEN = 'T'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">STATUS.WAITING = 'W'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local queue = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local old = rawget(_G,'queue')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if old then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken = old.taken
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid = old.bysid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._triggers = old._triggers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats = old._stats
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait = old._wait
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch = old._runch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runat = old._runat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._triggers = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait = fiber.channel()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch = fiber.cond()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while true do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue.index.status:pairs({STATUS.TAKEN}):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then break end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autoreleased %s at start", t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(STATUS) do queue._stats[v] = 0LL end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in box.space.queue:pairs() do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ t[F.status] ] = (queue._stats[ t[F.status] ] or 0LL)+1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Perform initial stat counts %s", box.tuple.new{ queue._stats })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local new_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repeat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new_id = clock.realtime64()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> until not box.space.queue:get(new_id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return new_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function keypack( key )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return msgpack.encode( key )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function keyunpack( data )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return msgpack.decode( data )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_replace = box.space.queue:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if old then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ old[ F.status ] ] = queue._stats[ old[ F.status ] ] - 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[ new[ F.status ] ] = queue._stats[ new[ F.status ] ] + 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_replace)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_truncate = box.space._truncate:on_replace(function(old,new)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if new.id == box.space.queue.id then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue._stats) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._stats[k] = 0LL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_truncate)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_connect = box.session.on_connect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.peer = box.session.peer()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_connect)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._triggers.on_disconnect = box.session.on_disconnect(function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.session.storage.destroyed = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local bysid = queue.bysid[ sid ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if bysid then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info( "disconnected %s from %s", box.session.id(), box.session.storage.peer )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while next(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for key, id in pairs(bysid) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Autorelease %s by disconnect", id);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bysid[key] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({t.id},{{'=', F.status, STATUS.READY }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue._triggers.on_disconnect)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._runat = fiber.create(function(queue, gen, old_fiber)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.runat.'..gen)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen and old_fiber and old_fiber:status() ~= 'dead' do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Waiting for old to die")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:wait(0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Started...")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remaining
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = clock.realtime()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _,t in box.space.queue.index.runat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs( {0}, { iterator = 'GT' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.runat > now then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> remaining = t.runat - now
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if t.status == STATUS.WAITING then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Runat: W->R %s",t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id }, {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { '=', F.status, STATUS.READY },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> { '=', F.runat, 0 },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Runat: bad status %s for %s", t.status, t.id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> box.space.queue:update({ t.id },{{ '=', F.runat, 0 }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remaining or remaining > 1 then remaining = 1 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:wait(remaining)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._runch:broadcast()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Finished")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, queue, package.reload.count, queue._runat)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._runch:broadcast()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_host = '127.0.0.1'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local graphite_port = 2003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue._monitor = fiber.create(function(gen)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.name('queue.mon.'..gen)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.yield()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local remote = require 'socket'.tcp_connect(graphite_host, graphite_port)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remote then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("Failed to connect to graphite %s",errno.strerror())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while package.reload.count == gen do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local data = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k,v in pairs(queue.stats()) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> table.insert(data,string.format("queue.stats.%s %s %sn",k,tonumber(v),math.floor(fiber.time())))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> data = table.concat(data,'')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not remote:send(data) then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.error("%s",errno.strerror())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fiber.sleep(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end, package.reload.count)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.put(data, opts)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local id = gen_id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local runat = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local status = STATUS.READY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if opts and opts.delay then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runat = clock.realtime() + tonumber(opts.delay)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status = STATUS.WAITING
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:put(true,0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :insert{ id, status, runat, data }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only=true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.take(timeout)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not timeout then timeout = 0 end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local now = fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while not found do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> found = box.space.queue.index.status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :pairs({STATUS.READY},{ iterator = 'EQ' }):nth(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not found then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local left = (now + timeout) - fiber.time()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if left &lt;= 0 then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue._wait:get(left)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if box.session.storage.destroyed then return end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local sid = box.session.id()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> log.info("Register %s by %s", found.id, sid)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local key = keypack( found.id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = sid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ] = queue.bysid[ sid ] or {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ sid ][ key ] = found.id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update( {found.id}, {{'=', F.status, STATUS.TAKEN }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local function get_task( id )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not id then error("Task id required", 2) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id = tonumber64(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local key = keypack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t = box.space.queue:get{id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not t then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task {%s} was not found", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if not queue.taken[key] then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s not taken by anybody", id ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue.taken[key] ~= box.session.id() then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> error(string.format( "Task %s taken by %d. Not you (%d)",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id, queue.taken[key], box.session.id() ), 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return t, key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.ack(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t, key = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue:delete{t.id}:tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.release(id, opts)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local t, key = get_task(id)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.taken[ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queue.bysid[ box.session.id() ][ key ] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local runat = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local status = STATUS.READY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if opts and opts.delay then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runat = clock.realtime() + tonumber(opts.delay)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status = STATUS.WAITING
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if queue._wait:has_readers() then queue._wait:put(true,0) end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return box.space.queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :update({t.id},{{'=', F.status, status },{ '=', F.runat, runat }})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> :tomap{ names_only = true }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function queue.stats()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total = box.space.queue:len(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ready = queue._stats[ STATUS.READY ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> waiting = queue._stats[ STATUS.WAITING ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> taken = queue._stats[ STATUS.TAKEN ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">return queue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">```**init.lua:**```
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require'strict'.on()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fiber = require 'fiber'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require 'package.reload'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.cfg{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen = '127.0.0.1:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queue = require 'queue'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if not fiber.self().storage.console then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> require'console'.start()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> os.exit()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div></content:encoded><author>Mons Anderson</author><category>Advanced Level</category><category>Code</category><category>DevOps</category><category>Lua</category><category>Open Source Databases</category><category>Programming</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/10/image5_hu_7352be48a0c72581.jpg"/><media:content url="https://percona.community/blog/2020/10/image5_hu_9b08d59deba03900.jpg" medium="image"/></item><item><title>Zero downtime schema change with Liquibase &amp; Percona</title><link>https://percona.community/blog/2020/10/26/zero-downtime-schema-change-with-liquibase-percona/</link><guid>https://percona.community/blog/2020/10/26/zero-downtime-schema-change-with-liquibase-percona/</guid><pubDate>Mon, 26 Oct 2020 14:14:50 UTC</pubDate><description>I am always surprised to learn something new whenever I talk to a member of the open-source community. No matter how much I think I have heard of every use case there is for Liquibase (and database change management in general), I always hear something that makes this space still feel new. There’s always something left to discover.</description><content:encoded>&lt;p>I am always surprised to learn something new whenever I talk to a member of the open-source community. No matter how much I think I have heard of every use case there is for &lt;a href="https://www.liquibase.org" target="_blank" rel="noopener noreferrer">Liquibase&lt;/a> (and database change management in general), I always hear something that makes this space still feel new. There’s always something left to discover.&lt;/p>
&lt;p>Today, that new something is the problem of how to perform large batches of changes with SQL ALTER TABLE statements. No problem you say? Okay, but this ALTER needs to happen in production. Still not worried? Well, let’s say you have millions of rows, and because you’re so successful, you have many transactions happening per minute (maybe even per second). Yeah…now we are talking. You can’t alter the table because you can’t afford to &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/alter-table.html" target="_blank" rel="noopener noreferrer">lock that table&lt;/a> for the 30 minutes (or more) it may take to execute the ALTER command.&lt;/p>
&lt;p>Well, what do you do? A Liquibase user just spoke to me about this very use case, and that they use &lt;a href="https://www.percona.com/doc/percona-toolkit/LATEST/index.html" target="_blank" rel="noopener noreferrer">Percona&lt;/a> with MySQL to solve this problem. (Thanks Erin Kolp!) In particular, &lt;a href="https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html" target="_blank" rel="noopener noreferrer">pt-online-schema-change&lt;/a> (which is a part of the &lt;a href="https://www.percona.com/software/database-tools/percona-toolkit" target="_blank" rel="noopener noreferrer">Percona Toolkit&lt;/a>) that allows you to perform the ALTER to a table without interrupting table access. Under the covers it makes a temporary table from the actual table being altered, makes the DDL change, then copies the data over, and swaps out the tables.&lt;/p>
&lt;p>Great! No more writing one-off scripts as a DBA to manage this problem! The advantage of using Percona may be obvious, but I think Percona said it best:&lt;/p>
&lt;p>“These tools are ideal alternatives to private or ‘one-off’ scripts, because they are professionally developed, formally tested, and fully documented. They are also fully self-contained, so installation is quick and easy, and no libraries are installed.”
Percona and Liquibase are kindred spirits. I’ve seen folks rip out their old school CI/CD setup for the database and replace it with Liquibase for the same reason. It was made and tested by a community so you can have confidence it works and you can concentrate on delivery.&lt;/p>
&lt;p>So now I have solved production interruption due to changes like alters that can cause tables to become unavailable, how do I automate this? By combining Liquibase with a &lt;a href="https://github.com/adangel/liquibase-percona" target="_blank" rel="noopener noreferrer">Liquibase/Percona extension&lt;/a> written by &lt;a href="https://github.com/adangel" target="_blank" rel="noopener noreferrer">Andreas Dangle&lt;/a>.
Here are the basic steps:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.liquibase.org/download" target="_blank" rel="noopener noreferrer">Download and install Liquibase&lt;/a>&lt;/li>
&lt;li>Install &lt;a href="https://www.percona.com/doc/percona-toolkit/LATEST/installation.html" target="_blank" rel="noopener noreferrer">Percona Toolkit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/adangel/liquibase-percona" target="_blank" rel="noopener noreferrer">Download the Percona Liquibase extension&lt;/a>&lt;/li>
&lt;li>Place the jar file in your “lib” directory in your Liquibase install directory.&lt;/li>
&lt;li>
&lt;figure>&lt;img width="623" height="374" sizes="(max-width: 623px) 100vw, 623px" srcset="https://percona.community/blog/2020/10/image1-1_hu_800032f82a658d31.webp 480w, https://percona.community/blog/2020/10/image1-1_hu_113ae689aae98514.webp 623w" src="https://percona.community/blog/2020/10/image1-1_hu_113ae689aae98514.webp" alt="Zero downtime schema change with Liquibase &amp; Percona" loading="lazy" />&lt;/figure>&lt;/li>
&lt;li>Update any changeset that needs to use Percona to include `usePercona:true` (see example below)&lt;/li>
&lt;li>Profit&lt;/li>
&lt;/ul>
&lt;h2 id="example">Example&lt;/h2>
&lt;p>Here, we want to add a column: Example:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;changeSet id="2" author="Alice">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">    &lt;addColumn tableName="person">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">        &lt;column name="address" type="varchar(255)"/>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">    &lt;/addColumn>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/changeSet>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Corresponding command that Liqubase would run: pt-online-schema-change –alter=“ADD COLUMN address VARCHAR(255)” … Enjoy all the PTO you get because your deployments happen super fast with no downtime. Hey in the meantime, why don’t you smack talk and shit post on social media? I’m available, I’ve got thick skin, and I’m online a bunch:&lt;/p>
&lt;ul>
&lt;li>Twitter: &lt;a href="https://twitter.com/RonakRahman" target="_blank" rel="noopener noreferrer">@ronakrahman&lt;/a>&lt;/li>
&lt;li>LinkedIn: &lt;a href="https://www.linkedin.com/in/ronak/" target="_blank" rel="noopener noreferrer">https://www.linkedin.com/in/ronak/&lt;/a>&lt;/li>
&lt;li>Discord: &lt;a href="https://discord.gg/9yBwMtj" target="_blank" rel="noopener noreferrer">https://discord.gg/9yBwMtj&lt;/a> (ronak#8065)&lt;/li>
&lt;li>Github: &lt;a href="https://github.com/ro-rah" target="_blank" rel="noopener noreferrer">https://github.com/ro-rah&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Ronak Rahman</author><category>ronak.rahman</category><category>Liquibase</category><category>MySQL</category><category>mysql-and-variants</category><category>Tools</category><category>Toolkit</category><media:thumbnail url="https://percona.community/blog/2020/10/image1-1_hu_a727cac9ef2c40d6.jpg"/><media:content url="https://percona.community/blog/2020/10/image1-1_hu_750e1a1d9d939c3e.jpg" medium="image"/></item><item><title>Mayastor: Lightning Fast Storage for Kubernetes</title><link>https://percona.community/blog/2020/10/23/mayastor-lightning-fast-storage-for-kubernetes/</link><guid>https://percona.community/blog/2020/10/23/mayastor-lightning-fast-storage-for-kubernetes/</guid><pubDate>Fri, 23 Oct 2020 14:03:08 UTC</pubDate><description>At MayaData we like new tech. Tech that makes our databases perform better. Tech like lockless ring buffers, NVMe-oF, and Kubernetes. In this blog post we’re going to see those technologies at work to give us awesome block storage performance with flexibility and simple operations.</description><content:encoded>&lt;p>At MayaData we like new tech. Tech that makes our databases perform better. Tech like &lt;a href="https://www.kernel.org/doc/Documentation/trace/ring-buffer-design.txt" target="_blank" rel="noopener noreferrer">lockless ring buffers&lt;/a>, &lt;a href="https://en.wikipedia.org/wiki/NVM_Express" target="_blank" rel="noopener noreferrer">NVMe-oF&lt;/a>, and &lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a>. In this blog post we’re going to see those technologies at work to give us awesome block storage performance with flexibility and simple operations.&lt;/p>
&lt;h2 id="mayastor--spdk--nvme--fast-databases">Mayastor + SPDK + NVMe = fast databases&lt;/h2>
&lt;p>Mayastor is new tech, it’s fast, and it’s based on &lt;a href="https://spdk.io/" target="_blank" rel="noopener noreferrer">SPDK&lt;/a>. Why is SPDK exciting? It’s a new generation in storage software, designed for super high speed low latency &lt;a href="https://en.wikipedia.org/wiki/NVM_Express" target="_blank" rel="noopener noreferrer">NVMe&lt;/a> devices. I’ll save you the scrolling and just tell you I believe Mayastor was able to max out the practical throughput of the nvme device I used for my benchmark, allowing for multiple high performance (20kqps+) database instances on a single node. Perfect for a database farm in Kubernetes&lt;/p>
&lt;h2 id="why-test-with-a-relational-db">Why Test With a Relational DB?&lt;/h2>
&lt;p>Open source relational databases are a staple component for app developers. People use them all the time for all kinds of software projects. It’s easy to build relationships between different groups of data, the syntax is well known, and they’ve been around for as long as modern computing.  When a dev wants a relational database to hack on, odds are good that it’s going to be &lt;a href="https://www.postgresql.org/" target="_blank" rel="noopener noreferrer">Postgres&lt;/a> or &lt;a href="https://www.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL&lt;/a>. They’re Free. They’re open source. They’ve both been quite stable for a long time, and they both run in Kubernetes just great. The good folks at Percona make containerized, production ready versions of these databases, and we’re going to use their &lt;a href="https://www.percona.com/software/mysql-database" target="_blank" rel="noopener noreferrer">Percona Distribution for MySQL&lt;/a> for the following tests.&lt;/p>
&lt;h2 id="kubernetes-and-the-learning-curve">Kubernetes and the Learning Curve&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="600" height="430" sizes="(max-width: 600px) 100vw, 600px" srcset="https://percona.community/blog/2020/10/image1_hu_d6fc434f4abf4f85.webp 480w, https://percona.community/blog/2020/10/image1_hu_e1f04e3522cd6136.webp 600w" src="https://percona.community/blog/2020/10/image1_hu_e1f04e3522cd6136.webp" alt="Mayastor 1" loading="lazy" />&lt;/figure>
So what is the difficulty with running relational databases, or databases in general, inside of Kubernetes?  Given all the features of Kubernetes for managing highly available application deployments: Automation with control, Common declarative configuration interface, and build-in observability, one would think Databases are the application to deploy to it. The main difficulty is storage. Until now.&lt;/p>
&lt;h2 id="dbs-are-often-io-bound">DBs are Often IO Bound&lt;/h2>
&lt;p>The trick is, databases are notoriously disk intensive and latency sensitive. The reason this has an impact on your Kubernetes deployments is that storage support in stock settings and untuned K8s clusters is rudimentary at best. That’s created a number of projects that are out to provide for storage in K8s projects, including, of course, the popular OpenEBS project.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodeSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fsGroup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1001&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">"20"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">8Gi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"--ignore-db-dir"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">"lost+found"&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">MYSQL_ROOT_PASSWORD&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">foobarbaz&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3306&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/var/lib/mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">percona-mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">persistentVolumeClaim&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">claimName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vol2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In this post I’m going to investigate the newest of the storage engines that comprise the data plane for OpenEBS. As a challenge, I’d like to be able to achieve 20,000 queries per second out of a MySQL database using this storage engine for block storage underneath.&lt;/p>
&lt;p>Now, getting to 20kqps could be easy with the right dataset. But I want to achieve this with data that’s significantly larger than available RAM. In that scenario, 20kqps is pretty fast (as you can see below by the disk traffic and cpu load it generates).&lt;/p>
&lt;p>There are a number of great options available for deploying MySQL in Kubernetes, but for this test we really just want a good, high performance database to start with. I won’t really need fancy DBaaS functionality, an operator to take care of backups, or anything of the sort. We’ll start from scratch with Percona’s MySQL container, and build a little deployment manifest for it. Now, maybe you’re thinking: “don’t you mean a stateful set?” But no, we’re going to use a deployment for this. Simple and easy to configure alongside of Container Attached Storage.&lt;/p>
&lt;p>The deployment pictured references an external volume, vol2. Now we could create a PV for this on the local system, but then if our MySQL instance gets scheduled on a different machine, the storage won’t be present.  &lt;/p>
&lt;h2 id="enter-mayastor">Enter Mayastor&lt;/h2>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;span class="code-block__lang">yaml&lt;/span>&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PersistentVolumeClaim&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vol2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mayastor-nvmf-fast&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">accessModes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ReadWriteOnce&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">20Gi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Mayastor is the latest storage engine for OpenEBS and MayaData’s Kubera offering. Mayastor represents the state-of-the-art in feature-rich storage for Linux systems. Mayastor creates virtual volumes that are backed by fast NVMe disks, and exports those volumes over the super-fast NVMf protocol. It’s a fresh implementation of the Container Attached Storage model. By &lt;a href="https://www.cncf.io/blog/2018/04/19/container-attached-storage-a-primer/" target="_blank" rel="noopener noreferrer">CAS&lt;/a>, I mean it’s purpose built for the multi-tenant distributed world of the cloud. CAS means each workload gets its own storage system, with knobs for tuning and everything. The beauty of the CAS architecture is that it decouples your apps from their storage. You can attach to a disk locally or via NVMf or iSCSI.&lt;/p>
&lt;p>Mayastor is CAS and it is purpose built to support cloud native workloads at speed with very little overhead. At MayaData we wrote it in Rust; we worked with Intel to implement new breakthrough technology called SPDK; made it easy to use with Kubernetes and possible to use with anything; and open-sourced it because, well, it improves the state of the art of storage in k8s and community always wins (eventually).&lt;/p>
&lt;p>If you’d like to set up Mayastor on a new or existing cluster, have a look at: &lt;a href="https://mayastor.gitbook.io/introduction/" target="_blank" rel="noopener noreferrer">https://mayastor.gitbook.io/introduction/&lt;/a>&lt;/p>
&lt;h2 id="the-speed-hypothesis">The Speed Hypothesis&lt;/h2>
&lt;p>The first thing I want to do is get an idea of how many queries per second (QPS) at which the DB maxes out. My suspicion at the outset is that the limiter for QPS is typically storage latency. We can deploy our Mayastor pool and storage class manifests in a small test cluster just to make sure they’re working as expected, and then tune our test to drive the DB as hard as we can. Performance characteristics of databases are very much tied to the specifics of the workload and table structure. So the first challenge here is to sort out what kind of workload is going to exercise the disk effectively.&lt;/p>
&lt;p>Sysbench is a great tool for exercising various aspects of Linux systems, and it includes some database tests we can use to get some baselines. &lt;a href="https://github.com/akopytov/sysbench" target="_blank" rel="noopener noreferrer">https://github.com/akopytov/sysbench&lt;/a> is where you can find it. We can put it in a container and point that mysql OLTP test right at our database service.&lt;/p>
&lt;p>After a little bit of experimentation with sysbench options to set different values for the table size, number of tables, etc., I arrived at very stable results on a small cluster in AWS using m5ad.xlarge nodes. I’ve settled on 10 threads and 10 tables, with 10M rows in each table. With no additional tuning on MySQL, sysbench settles into about 4300 queries per second with an average latency at 46ms. Pretty good for a small cloud setup.&lt;/p>
&lt;p>With that as a baseline, let’s see how much we can get out of it on a larger system. Intel makes high core-count cpus and very fast Optane NVMe devices, and they’ve generously allowed us to use their benchmarking labs for a little while for some database testing. Without going into too much hardware geekery, we have three 96 core boxes running at 2.2Ghz with more RAM than I need and 100Gb networking to string them together. Each box has a small Optane NVMe device, and this single little drive is capable of at least 400k iops and 1.7GB/s through an ext4 filesystem. That’s fast. The published specs for this device are a little bit higher (about 500k iops and 2GB/s) but we’ll take this to be peak perf for our purposes.&lt;/p>
&lt;h2 id="results-of-the-first-test">Results of the First Test&lt;/h2>
&lt;p>For the first test, just to characterize the setup, I threw 80 or so cores at the database, and ran sysbench against it with a whole lot of threads. Like 300.&lt;/p>
&lt;p>I started with a smaller table size just to save a little time on the load phase.  It took a few iterations to get the test to run - adjustments to &lt;code>max_connections&lt;/code>. The smaller table size means it might fit into memory, but it’ll test our test framework quickly.  Sure enough, running our OLTP test gets us close to 100k queries per second. But, there’s no real disk activity. We need more data in order to test the underlying disks.&lt;/p>
&lt;p>I cranked up the table size to 20,000,000 rows per table, tuned Mayastor to use three of the cores on each box, and started tuning the test to get max queries per second out of it. Three tables seem to be enough to overflow the 8G of RAM we have allocated to the container. Now when I check the disk stats on the node, there’s plenty of storage traffic. Still less than a gigabyte per second though. The system settles down into a comfortably speedy 30kqps or thereabouts, with disk throughput right around 700MB/s and a latency right around 50ms per query. Curiously the database is using about 8 cores. Clearly we don’t need to allocate all 80.&lt;/p>
&lt;p>We’ve seen more than 700MB/s out of the storage already from our synthetic tests. That’s pretty far off of the peak measured perf of 1.7GB/s.&lt;/p>
&lt;h2 id="i-wonder-if-we-can-get-another-mysql-on-here">I wonder if we can get another MySQL on here…&lt;/h2>
&lt;p>Sure enough, this system is fast enough to host two high performance relational database instances on the same nvme drive, with cpu to spare.  If only I had another one of those NVMe drives in this box….&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1999" height="1123" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/10/image3_hu_4ee27d9f0013ad39.webp 768w, https://percona.community/blog/2020/10/image3_hu_dccbd2dbc33397f0.webp 480w, https://percona.community/blog/2020/10/image3_hu_9fbb09ed0260aff9.webp 1400w" src="https://percona.community/blog/2020/10/image3_hu_9fbb09ed0260aff9.webp" alt="A screenshot showing Mayastor in action" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>That’s about 1.1GB/s, with 52k IOPs. Not bad. We might even be able to fit a third in if we’re willing to sacrifice a little bit of speed across all the instances.&lt;/p>
&lt;p>There’s more work to be done to characterize database workloads like this one. There’s also an opportunity to investigate why the database scales up to 20-30k IOPs but leaves some storage and system resources available.&lt;/p>
&lt;p>Perhaps most importantly - Maystor provides a complete abstraction for kubernetes volumes, and allows for replicating to multiple nodes, snapshotting volumes, encrypting traffic, and generally everything you’ve come to expect from enterprise storage.  Mayastor is showing the promise here of LocalPV like performance - at least maxing out the capabilities of our DB as configured - while also providing the ease of use and ability to add resilience.&lt;/p>
&lt;p>Lastly, if you are interested in Percona and OpenEBS, there are a lot of blogs from the OpenEBS community and a recent one by the CTO of Percona on the use of OpenEBS LocalPV as their preferred LocalPV solution here: &lt;a href="https://www.percona.com/blog/2020/10/01/deploying-percona-kubernetes-operators-with-openebs-local-storage/" target="_blank" rel="noopener noreferrer">https://www.percona.com/blog/2020/10/01/deploying-percona-kubernetes-operators-with-openebs-local-storage/&lt;/a>&lt;/p>
&lt;p>The &lt;a href="https://forums.percona.com/categories/percona-distribution-for-mysql" target="_blank" rel="noopener noreferrer">Percona Community Forum&lt;/a>, &lt;a href="https://openebs.io/community/" target="_blank" rel="noopener noreferrer">OpenEBS&lt;/a>, and &lt;a href="https://dok.community/" target="_blank" rel="noopener noreferrer">Data on Kubernetes&lt;/a>communities are increasingly overlapping and I hope and expect this write up will result in yet more collaboration. Come check out Check out &lt;a href="https://mayastor.gitbook.io/introduction/" target="_blank" rel="noopener noreferrer">Mayastor&lt;/a> on your own and let us know how Mayastor works for your use case in the comments below!&lt;/p>
&lt;p>Brian Matheson has spent twenty years doing things like supporting developers, tuning networks, and writing tools. A serial entrepreneur with an intense customer focus, Brian has helped a number of startups in a technical capacity. You can read more of Brian’s blog posts at &lt;a href="https://blog.mayadata.io/author/brian-matheson" target="_blank" rel="noopener noreferrer">https://blog.mayadata.io/author/brian-matheson&lt;/a>.&lt;/p></content:encoded><author>Brian Matheson</author><category>Kubernetes</category><category>Kubernetes</category><category>MayaData</category><category>Mayastor</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/10/image1_hu_73722df26ba683eb.jpg"/><media:content url="https://percona.community/blog/2020/10/image1_hu_ef94108f054a471a.jpg" medium="image"/></item><item><title>MySQL 8.0 Document Store, Discovery of a New World – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/19/mysql-8-0-document-store-discovery-of-a-new-world-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/19/mysql-8-0-document-store-discovery-of-a-new-world-percona-live-online-talk-preview/</guid><pubDate>Mon, 19 Oct 2020 14:01:12 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 5:00 a.m. • London 10:00 a.m. • New Delhi 2:30 p.m. • Singapore 5:00 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 5:00 a.m. • London 10:00 a.m. • New Delhi 2:30 p.m. • Singapore 5:00 p.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>MySQL Document Store enables us to work with SQL relational tables and schema-less JSON collections. So instead of having a mixed bag of databases, you can just rely on MySQL, where the JSON documents can be stored in collections and managed with CRUD operations. All you need to do is install the X plugin. In this session, you will learn what a document store is, how to install and use it, and all the reasons for considering it. We will also see several specific features helping developers and illustrate how the usual MySQL DBA can manage this new world.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This talk is very exciting because it’s focus on new capabilities that is available only in MySQL and that many people are not aware of it. Every time I talk about that topic, the audience is really surprised and enthusiast about MySQL Document Store. It’s not common to have a JSON document store will all the capabilities of MySQL, fully transactional but at the same time using CRUD operations where you can mix your relational data and your schemaless document in the same query.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>This particular talk is more focused on developers but I tried to also include content for DBAs. However it’s not a talk oriented on operators like I usually do during Percona Live shows.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;p>I’m looking forward to hear again from René and ProxySQL, a project that I really appreciate. And also I’m curious to see which recommendation Øystein will provide for MySQL analytics queries.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>I will also have the honor to present the State of the Dolphin during the show, don’t miss it if you want to learn about MySQL 8.0 and our Community. Of course I won’t deliver a full list of features as it would take almost the full conference time ;)&lt;/p></content:encoded><author>Frédéric Descamps</author><category>frederic.descamps</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>The State of ProxySQL, 2020 Edition – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/16/the-state-of-proxysql-2020-edition-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/16/the-state-of-proxysql-2020-edition-percona-live-online-talk-preview/</guid><pubDate>Fri, 16 Oct 2020 20:08:01 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 7:30 a.m. • London 12:30 p.m. • New Delhi 5:00 p.m. • Singapore 7:30 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 7:30 a.m. • London 12:30 p.m. • New Delhi 5:00 p.m. • Singapore 7:30 p.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>ProxySQL is a high performance, high available, protocol aware proxy for MySQL. 2.0 has been GA for some time now, and there have been a lot of changes that come in point releases as well, such that you can benefit from them. Listen to René, the founder of ProxySQL, take you through some of the new features in 2.0, and how you can effectively utilize them. Some topics that are covered include: - LDAP authentication - SSL for client connections - AWS Aurora usage - Native clustering support for Percona XtraDB Cluster (PXC) / Galera Cluster / group replication - Kubernetes deployments This is the talk to take you from intermediate ProxySQL user to expert in the 2.0 feature set. There will also be talk about the roadmap for what is coming next.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This talk is exciting because it will bring to the community all the latest features in ProxySQL, and short term plans for even more exciting features.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Devs, DBAs, sysadmins: all users interested in optimizing and manging traffic against MySQL backends can benefit from this talk, no matter their level of experience.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>All the talks on the Percona Live agenda are exciting, but the two talks that rank very high in my list are “&lt;a href="https://sched.co/eouq" target="_blank" rel="noopener noreferrer">Why Public Database as a Service is Prime for Open Source Disruption&lt;/a>” by Peter Zaitsev, and “&lt;a href="https://sched.co/ePpr" target="_blank" rel="noopener noreferrer">Sharding: DIY or Out of the Box Solution?&lt;/a>” by Art van Scheppingen.&lt;/p></content:encoded><author>René Cannaò</author><category>rene.cannao</category><category>MySQL</category><category>PLO-2020-10</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Engineering Data Reliably Using SLO Theory – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/15/engineering-data-reliably-using-slo-theory-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/15/engineering-data-reliably-using-slo-theory-percona-live-online-talk-preview/</guid><pubDate>Thu, 15 Oct 2020 02:43:47 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 12:30 p.m. • London 5:30 p.m. • New Delhi 10:00 p.m. • Singapore 12:30 a.m. (next day)</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Tue 20 Oct • New York 12:30 p.m. • London 5:30 p.m. • New Delhi 10:00 p.m. • Singapore 12:30 a.m. (next day)&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>Not so long ago, operations specialists worked much like today’s data engineers do: with specialized skills, they were the people who kept sites running, who responded to emergencies, and who—unfortunately—spent much of their time dealing with incidents and other “fires.” When the DevOps revolution came, this began to change. Better tools, better practices, and better culture shaped how Ops folks worked. A subset of that DevOps culture soon emerged: Site Reliability Engineers. These were people whose focus was not just on the day-to-day deployment of applications, but running platforms, products, and services with very high performance, very large scale, and with very high demand for reliability. Data Engineering was left out of this revolution.&lt;/p>
&lt;p>But it is not too late! By taking concepts from SRE culture, in particular, the theory of Service Level Objectives, we look at how teams operating and developing data platforms and data products can be built more reliably through the use of quantitative measures and product thinking. This talk will discuss concrete examples of the benefits of this approach for data teams and how organizations can benefit from this mindset.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>I see a lot of the same pains over and over in the data engineering world: data engineers spending too much time firefighting or dealing with ad hoc requests to innovate, data scientists pained by long lead times for pipeline engineering, and poor data quality eroding trust and leading organizations to make “gut” decisions instead of data-driven ones. This doesn’t have to be our world. The reality that many data engineers face today is similar to the one ops folks faced years ago, before Site Reliability Engineering (SRE) practices began to solidify. However, most of the learnings in the SRE space, particularly Service Level Objective (SLO) theory, don’t translate directly to the data space unless we adapt them to our unique reality. But if we can build solid, data-driven best practices, we can achieve so much—less firefighting, more creation; less guesswork, more trust.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Certainly, data engineers will benefit. But also managers, executives, and product owners will all benefit from learning how we can deliberately craft data engineering practices to optimize for reliability. Data should be a business driver, but too often I see it as a cost center. We need to change that calculus.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;p>I’m excited to see &lt;a href="https://sched.co/eouw" target="_blank" rel="noopener noreferrer">Karen Ambrose’s talk&lt;/a>. I think that building technology to address rapidly-evolving crises is an enormous challenge, and frankly, I think that maybe a lot of organizations have been too complacent and risk averse to manage rapid pivots. I’m really curious to hear the story about how people came together to change the status quo in an effort to literally save the world.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>There’s a Millennial Prize Problem or two still unsolved, and I’d love to answer one of those.&lt;/p></content:encoded><author>Emily Gorcenski</author><category>emily.gorcenski</category><category>DevOps</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>DBdeployer, the Community Edition – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/14/dbdeployer-the-community-edition-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/14/dbdeployer-the-community-edition-percona-live-online-talk-preview/</guid><pubDate>Wed, 14 Oct 2020 18:44:09 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 3:30 a.m. • London 8:30 a.m. • New Delhi 1:00 p.m. • Singapore 3:30 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 3:30 a.m. • London 8:30 a.m. • New Delhi 1:00 p.m. • Singapore 3:30 p.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>&lt;a href="https://github.com/datacharmer/dbdeployer" target="_blank" rel="noopener noreferrer">DBdeployer&lt;/a>, an open source tool that allows easy deployment of many MySQL/Percona servers in the same host, has passed two years of development. Its latest additions have aimed at improving ease of use for both beginners and experts. This talk will show how to start with dbdeployer with an empty box, and quickly populate it with recent and less recent server versions, all at the command line.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This talk is a celebration of collaboration in the community. I will present recent features that were requested, or suggested, by the community. I will also show how those suggestions came to fruition, to encourage more of the same from attendees.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Any current or future user of dbdeployer. They will see the process of refining the usability of the tool through interaction with the community.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>There is a recurring question that I get from people who are about to use dbdeployer but haven’t gotten to know it well: “is it cloud friendly?” It pains me to answer that it isn’t, not because of a deficiency of the tool, but because it was designed to stay out of the cloud. Using dbdeployer, the cloud is your laptop, or the tiny Linux server in your workroom. The main purpose of dbdeployer is to enable developers, support engineers, QA engineers, database administrators, to have on demand deployments of MySQL always, even when there is no connection with the cloud or when you want something faster than the cloud.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;p>Most of the talks and keynote are promising. I look forward in particular to watch “&lt;a href="https://sched.co/ePp6" target="_blank" rel="noopener noreferrer">Vitess Online Schema Migration Automation&lt;/a>” by Shlomi Noach and “&lt;a href="https://sched.co/ePpc" target="_blank" rel="noopener noreferrer">MySQL 8.0 Document Store - Discovery of a New World&lt;/a>” by Frédéric Descamps.&lt;/p></content:encoded><author>Giuseppe Maxia</author><category>Events</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Sharding: DIY or Out of the Box Solution? – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/13/sharding-diy-or-out-of-the-box-solution-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/13/sharding-diy-or-out-of-the-box-solution-percona-live-online-talk-preview/</guid><pubDate>Tue, 13 Oct 2020 03:04:37 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 7:00 a.m. • London 12:00 noon • New Delhi 4:30 p.m. • Singapore 7:00 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 7:00 a.m. • London 12:00 noon • New Delhi 4:30 p.m. • Singapore 7:00 p.m.&lt;/em>&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>I’m not sure if my talk is exciting, but I’m quite positive the subject is! Vitess has been gaining a lot of traction over the past few years and I must admit that I’ve been keen to get hands-on experience with it for years. As we, MessageBird, encounter rapid growth standard (read) scaling wasn’t applicable anymore and we were in need for a solution. Late 2019 we implemented our (quick) DIY sharding solution based upon existing components. A few months later we encountered the next scaling issue and we found our own built solution wasn’t suitable in this case. That’s when we considered investing our time instead in a Vitess proof of concept (community edition) and this talk will compare the two paths chosen and show some of the choices and compromises you have to make.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>People who need to shard their (write) workloads and are considering using Vitess for this purpose. Our intention is to do a fair comparison between the two to help others make a well prepared decision.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>The agenda is full of presentations I’m looking forward to. However as my current world is dominated by productionalising Vitess, I will be looking forward to &lt;a href="https://perconaliveonline2020.sched.com/event/ePp6/vitess-online-schema-migration-automation" target="_blank" rel="noopener noreferrer">Shlomi Noach’s talk&lt;/a> about online schema migrations automation in Vitess.&lt;/p></content:encoded><author>Art van Scheppingen</author><category>art.vanscheppingen</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>What If We Could Use Machine Learning Models as Tables – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/12/what-if-we-could-use-machine-learning-models-as-tables-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/12/what-if-we-could-use-machine-learning-models-as-tables-percona-live-online-talk-preview/</guid><pubDate>Mon, 12 Oct 2020 17:56:51 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 1:30 p.m. • London 6:30 p.m. • New Delhi 11:00 p.m. • Singapore 1:30 a.m. (next day)</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Tue 20 Oct • New York 1:30 p.m. • London 6:30 p.m. • New Delhi 11:00 p.m. • Singapore 1:30 a.m. (next day)&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>In most machine learning tasks, one has to first organize data in some form and then turn it into information about the problem that needs to be solved. One could say that the requirement to train many machine learning algorithms is information, not just data. Given that most of the world’s structured and semi-structured data (information) lives in databases, it makes sense to bring ML capabilities straight to the databases themselves. In this talk we want to present to the Percona community what we have learned in the effort of enabling existing databases like MariaDB and Postgres with frictionless ML powers.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>ML straight in databases is exciting because it enables hundreds of thousands of people that already know SQL to solve problems using machine learning without any extra skills.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Anyone knows how to query a SQL database.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>What databases can we do machine learning in now, and which ones are coming?&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://perconaliveonline2020.sched.com/#" target="_blank" rel="noopener noreferrer">The Cloud is Inevitable&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://perconaliveonline2020.sched.com/#" target="_blank" rel="noopener noreferrer">Serverless Databases: The Good, the Bad, and the Ugly &lt;/a>&lt;/li>
&lt;li>&lt;a href="https://perconaliveonline2020.sched.com/#" target="_blank" rel="noopener noreferrer">The State of MongoDB, Its Open Source Community, and Where Percona Is Going With It&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Jorge Torres</author><category>jorge.torres</category><category>MariaDB</category><category>PLO-2020-10</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Vitess Online Schema Migration Automation – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/09/vitess-online-schema-migration-automation-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/09/vitess-online-schema-migration-automation-percona-live-online-talk-preview/</guid><pubDate>Fri, 09 Oct 2020 17:28:19 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 2:30 a.m. • London 7:30 a.m. • New Delhi 12:00 noon • Singapore 2:30 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 2:30 a.m. • London 7:30 a.m. • New Delhi 12:00 noon • Singapore 2:30 p.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>For many, running an online schema migration operation is still a manual job: from building the correct command, through identifying where the migration should run and which servers are to be affected, to auditing progress and completing the migration. Sharded environment poses an additional burden, as any logical migration must be applied multiple times, once for each shard.&lt;/p>
&lt;p>What if you could just issue an ALTER TABLE … statement, and have all that complexity automated away? Vitess, an open source sharding framework for MySQL, is in a unique position to do just that. This session shows how Vitess’s proxy/agent/topology architecture, together with gh-ost, are used to hide schema change complexity, and carefully schedule and apply schema migrations.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>My work unifies multiple open source solutions (gh-ost, freno, and others) in a single, managed place. Vitess becomes an infrastructure solution, which can automate away the complexities of schema migrations: running, tracking, handling errors, cleaning up. It offers a completely automated cycle for most users, yet still gives them the controls.&lt;/p>
&lt;p>Whether with gh-ost or pt-online-schema-change, vitess is able to abstract away the migration process such that the user will normally just run and forget. Having worked as an operational engineer, and having developed schema migration automation in my past experience, I’m just excited to think about the users who will save hours of manual labor a week with this new offering.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Operational DBAs and engineers who perform manual schema migrations, or are looking to automate their database infrastructure.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;p>I’m in particular curious to hear about what’s new in distributed databases and geo replication. Otherwise, as always, I’m keen to hear about open source tools in the MySQL ecosystem.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>Q: Is this work public? A: Yes, it is. This work is expected to be released as an experimental feature as part of Vitess 8.0, end of October 2020. It is public, free and open source.&lt;/p></content:encoded><author>Shlomi Noach</author><category>shlomi.noach</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Analytical Queries in MySQL – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/09/analytical-queries-in-mysql-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/09/analytical-queries-in-mysql-percona-live-online-talk-preview/</guid><pubDate>Fri, 09 Oct 2020 17:05:14 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 6:00 p.m. • London 11:00 p.m. • New Delhi 3:30 a.m. • Singapore 6:00 a.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Tue 20 Oct • New York 6:00 p.m. • London 11:00 p.m. • New Delhi 3:30 a.m. • Singapore 6:00 a.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>MySQL’s sweet spot is known to be online transaction processing (OLTP), and it can support a very high load of short transactions. Many users will also want to run analytical queries (OLAP) on their MySQL data. Often they achieve this by exporting their data to another database system that is tailored for analytical queries. However, this introduces overhead and delay that can be avoided by running your analytical queries directly in your MySQL database.&lt;/p>
&lt;p>This presentation will discuss how you can tune your complex analytical queries to achieve better performance with MySQL. We will look at some of the queries from the well-known TPC-H/DBT-3 benchmark, and show how we can improve the performance of these queries through query rewrites, optimizer hints, and improved configuration settings.&lt;/p>
&lt;p>We will also compare the performance of these queries to other database systems like MariaDB and PostgreSQL, and discuss how MySQL could be improved to better support these queries. While this presentation will mainly focus on MySQL, we will also compare with MariaDB and Postgres and discuss what causes the difference in performance between the systems.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This talk is exciting because we will show several ways you can improve the performance of complex queries in MySQL. We will also compare the performance of MySQL to other database systems, and discuss what MySQL could learn from those systems.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Developers who use MySQL will learn how to write more efficient queries, and DBAs will learn how to tune their systems for better performance of complex queries. People that are interested in implementation aspects of database systems, should find the discussion of what.can be learned from other database systems interesting.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>I look forward to the other presentations on analytical queries: “&lt;a href="https://sched.co/ePo2" target="_blank" rel="noopener noreferrer">SQL Row Store vs Data Warehouse: Which Is Right for Your Application?&lt;/a>” by Robert Hodges, and “&lt;a href="https://sched.co/ePr2" target="_blank" rel="noopener noreferrer">Building Data Lake with MariaDB ColumnStore&lt;/a>” by Sasha Vaniachine. (However, I will probably not get up at 5:30am to watch the latter live :-)).&lt;/p>
&lt;p>I also look forward to the presentations on “&lt;a href="https://sched.co/eN9q" target="_blank" rel="noopener noreferrer">How Can Databases Capitalize on Computational Storage?&lt;/a>” by Tong Zhang and JB Baker, and “&lt;a href="https://sched.co/ePo7" target="_blank" rel="noopener noreferrer">How to Protect the SQL Engine From Running Out of Memory&lt;/a>” by Huaiyu Xu and Song Gao.&lt;/p></content:encoded><author>Øystein Grøvlen</author><category>oystein.grovlen</category><category>Events</category><category>MariaDB</category><category>MySQL</category><category>PLO-2020-10</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Serverless Databases: The Good, the Bad, and the Ugly – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/08/serverless-databases-the-good-the-bad-and-the-ugly-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/08/serverless-databases-the-good-the-bad-and-the-ugly-percona-live-online-talk-preview/</guid><pubDate>Thu, 08 Oct 2020 20:58:38 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Oct • New York 4:30 a.m. • London 9:30 a.m • New Delhi 2:00 p.m. • Singapore 4:30 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Wed 21 Oct • New York 4:30 a.m. • London 9:30 a.m • New Delhi 2:00 p.m. • Singapore 4:30 p.m.&lt;/em>&lt;/p>
&lt;h2 id="abstract">Abstract&lt;/h2>
&lt;p>Starting with AWS, the major cloud providers offer different options to run a MySQL or a MySQL-compatible database on the cloud. A new approach is to rely on so-called serverless (relational) databases like Aurora Serverless that offer both traditional TCP connections and HTTP API access. Can serverless really be the future? Can data API really replace a MySQL connector? What are the major limitations of a serverless database cluster and do they really protect from inefficient use of database resources?&lt;/p>
&lt;h2 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h2>
&lt;p>The database is the most challenging layer to optimize resources in the cloud and achieve elasticity. Serverless relational databases can help in that but introduce as well new limitations and challenges, including cloud vendor lock-in. We will discuss the good, the bad and the ugly of running a MySQL database serverless.&lt;/p>
&lt;h2 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h2>
&lt;p>DevOps and cloud architects, almost all the lazy ones. The ones who would like to hide the complexity of managing a relational database on the cloud and optimise price-performances on their deployments with the click of a button.&lt;/p>
&lt;h2 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h2>
&lt;p>Many exciting topics in the agenda but I am really looking forward to “&lt;a href="https://sched.co/eN9q" target="_blank" rel="noopener noreferrer">How Can Databases Capitalize on Computational Storage?&lt;/a>” and “&lt;a href="https://sched.co/ePnR" target="_blank" rel="noopener noreferrer">MySQL Ecosystem on ARM&lt;/a>” among many others. For the keynotes, I am very interested in “&lt;a href="https://sched.co/eov2" target="_blank" rel="noopener noreferrer">The Cloud is Inevitable&lt;/a>” and I am looking forward to Peter’s one as well ("&lt;a href="https://perconaliveonline2020.sched.com/#" target="_blank" rel="noopener noreferrer">Why Public Database as a Service is Prime for Open Source Disruption").&lt;/a>&lt;/p></content:encoded><author>Renato Losio</author><category>renato-losio</category><category>AWS</category><category>Events</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>MySQL Ecosystem on ARM – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/07/mysql-ecosystem-on-arm-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/07/mysql-ecosystem-on-arm-percona-live-online-talk-preview/</guid><pubDate>Wed, 07 Oct 2020 02:28:58 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 8:00 p.m. • London 1:00 a.m (next day) • New Delhi 5:30 a.m. • Singapore 8:00 a.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Tue 20 Oct • New York 8:00 p.m. • London 1:00 a.m (next day) • New Delhi 5:30 a.m. • Singapore 8:00 a.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract?&lt;/h3>
&lt;p>The ARM ecosystem is quickly evolving as a cost-effective alternative to run High-Performance Computing (HPC) software. It continues to grow with some major cloud players hosting ARM-based cloud servers. MySQL too joined the ecosystem with 8.x. MariaDB already has made its presence. But besides the mainline server, a lot of tools are yet to get ported to ARM.&lt;/p>
&lt;p>In this talk, we will explore what all aspects of the MySQL ecosystem are part of ARM, work in progress, optimization being done for ARM, challenges involved, Is it safe to run MySQL (or its variant) on ARM?, community and industry support, performance aspect (especially with x86_64), etc.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>MySQL recently added support for ARM (starting 8.x). ARM on the other hand is gaining popularity as a cost-effective solution for running High-Performance Computing Software with multiple cloud providers (Huawei, Amazon, Oracle cloud) providing ARM instances. The community is excited to learn how the MySQL ecosystem is evolving on ARM and what kind of advantage users could get by running it on ARM.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Talk is mainly meant for end-user/DBA/dev-ops all those who need to decide how to optimally deploy MySQL and still ensure maximum throughput. The talk will explore the pros and cons of running MySQL on ARM and supporting ecosystems that should give audiences fair ideas if it is time for them to consider the said route.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>Percona Live, as always, has a lined up number of good and new talks. Personally, I am interested in checking MySQL deployment that scales geographically. Users are not only moving to the cloud but also considering if the said setup could now be globalized through geo-distribution keeping a tight check on cost especially with the situation of the current pandemic that has forced all businesses to re-look at their spending. Managing Database @ Scale, Best Practices in Design, and Implementing MySQL Geographic Distributed HA solutions are some of my short-lists to attend.&lt;/p>
&lt;h3 id="is-there-any-other-question-you-would-like-to-answer">Is there any other question you would like to answer?&lt;/h3>
&lt;p>I think there are a plethora of options available for users in the DB ecosystem space and the ecosystem is evolving at a pretty good pace. My only message to the users is to keep all options open and be flexible because you never know which options may work wonders for you. With an open-source ecosystem, try/experiment with new things is the key.&lt;/p></content:encoded><author>Krunal Bauskar</author><category>krunal.bauskar</category><category>Events</category><category>MariaDB</category><category>MySQL</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>NoSQL Endgame – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/05/nosql-endgame-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/05/nosql-endgame-percona-live-online-talk-preview/</guid><pubDate>Mon, 05 Oct 2020 01:08:04 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 3:00 p.m. • London 8:00 p.m. • New Delhi 12:30 a.m. (next day) • Singapore 3:00 a.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online Agenda&lt;/a> Slot: Tue 20 Oct • New York 3:00 p.m. • London 8:00 p.m. • New Delhi 12:30 a.m. (next day) • Singapore 3:00 a.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>The amount of data collected by applications nowadays is growing at a scary pace. Many of them need to handle billions of users generating and consuming data at an incredible speed. Maybe you are wondering how to create an application like this? What is required? What works best for your project?&lt;/p>
&lt;p>In this session we’ll compare popular Java and JVM persistence frameworks for NoSQL databases: Spring Data, Micronaut Data, Hibernate OGM, Jakarta NoSQL, and GORM. How do they compare, what are the strengths, weaknesses, differences, and similarities? We’ll show each of them with a selection of different NoSQL database systems (Key-Value, Document, Column, Graph).&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>The data load on applications has increased exponentially in recent years. We know the JVM (Java Virtual Machine) can cope with heavy loads very well yet we often come across the big dilemma: there are tons of persistence frameworks out there but which one performs best for my case? It would normally take ages to evaluate and choose the best fit for your use case. We’ve done those comparisons for you.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Those who make technical roadmap decisions such as software architects, engineering managers, and developers involved in new technology decisions.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What other talks are you most looking forward to?&lt;/h3>
&lt;p>The conference agenda is simply amazing. It’s difficult to choose which sessions to attend, but we’re pretty sure we’ll attend “&lt;a href="https://sched.co/ePlw" target="_blank" rel="noopener noreferrer">The 411 PMM&lt;/a>” by Brandon Fleisher and Steve Hoffman, and also “&lt;a href="https://sched.co/eN9q" target="_blank" rel="noopener noreferrer">How Can Databases Capitalize on Computational Storage&lt;/a>” by Tong Zhang and JB Baker.&lt;/p></content:encoded><author>Thodoris Bais</author><author>Werner Keil</author><category>thodoris.bais</category><category>werner.keil</category><category>Entry Level</category><category>Events</category><category>Open Source Databases</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Kunlun Distributed DB Cluster Intro – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/02/kunlun-distributed-db-cluster-intro-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/02/kunlun-distributed-db-cluster-intro-percona-live-online-talk-preview/</guid><pubDate>Fri, 02 Oct 2020 22:25:34 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 20 Oct • New York 9:30 p.m. • London 02:30 a.m. (next day) • New Delhi 7:00 a.m. • Singapore 09:30 a.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Tue 20 Oct • New York 9:30 p.m. • London 02:30 a.m. (next day) • New Delhi 7:00 a.m. • Singapore 09:30 a.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>Kunlun Distributed Database Cluster is a distributed DBMS that aims to combine the best of both MySQL and PostgreSQL for a highly performant, highly available, highly scalable and fault-tolerant, easy to use and manage database system that requires minimal human maintenance.  It enables users to define table sharding rules so that it automatically distributes tables properly to available storage shards; implements the two-phase commit protocol to do distributed transaction commit; uses MySQL group replication for high availability in storage shards; fixes a series of MySQL XA bugs to make distributed transactions highly reliable, among many other enhancements.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>Audience will get to know Kunlun — a brand new distributed database cluster built from two most popular open source database systems — MySQL and PostgreSQL, and how Kunlun can make developers and DBAs’ life much easier. They will also know why it’s troublesome and error prone to use MySQL group replication as is and how Kunlun make it totally easy by hiding all the complexity.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Application developers and DBAs who use MySQL and/or PostgreSQL clusters, especially those having to deal with multi terabytes of relational data that no single db instance can manage.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>Those about alternative ways to deal with ever growing and multi terabytes of relational data which far exceeds the capacity of a single db instance.&lt;/p></content:encoded><author>David Zhao</author><category>david.zhao</category><category>Events</category><category>MySQL</category><category>Open Source Databases</category><category>PLO-2020-10</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>MariaDB 10.5 New Features for Troubleshooting – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/10/01/mariadb-10-5-new-features-for-troubleshooting-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/10/01/mariadb-10-5-new-features-for-troubleshooting-percona-live-online-talk-preview/</guid><pubDate>Thu, 01 Oct 2020 23:42:03 UTC</pubDate><description>Percona Live Online Agenda Slot: Wed 21 Aug • New York 12:00 midnight • London 05:00 a.m. • New Delhi 9:30 a.m. • Singapore 12:00 noon</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Wed 21 Aug • New York 12:00 midnight • London 05:00 a.m. • New Delhi 9:30 a.m. • Singapore 12:00 noon&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>I want to help DBAs and Support engineers find out what’s really going on when some problem strikes.  My goal is to show new ways to diagnose problems now available in MariaDB 10.5.   See &lt;a href="https://perconaliveonline2020.sched.com/event/ePoK/mariadb-105-new-features-for-troubleshooting" target="_blank" rel="noopener noreferrer">the full abstract&lt;/a> for more.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why Is Your Talk Exciting?&lt;/h3>
&lt;p>It provides a lot of details and practical examples of how MariaDB 10.5 new features for troubleshooting may help DBAs and developers in understanding the production load and tuning MariaDB server for it. The process of documenting these new features is not completed yet, so you may not be able to easily find the information presented elsewhere.&lt;/p>
&lt;h3 id="who-would-benefit-from-your-talk">Who Would Benefit From Your Talk?&lt;/h3>
&lt;p>DBAs and consultants who use or plan to use MariaDB server 10.5 in production.&lt;/p>
&lt;h3 id="what-is-the-most-useful-new-feature-in-mariadb-105">What Is the Most Useful New Feature in MariaDB 10.5?&lt;/h3>
&lt;p>For me it’s memory instrumentation. There are alternative ways to find memory leaks or trace memory allocations in detail, but they either have notable performance impacts or are hard to implement in production. This feature potentially can bring DBAs many insights and help to resolve nasty problems. I’ve missed it for years.&lt;/p>
&lt;h3 id="what-other-talks-are-you-most-looking-forward-to">What Other Talks Are You Most Looking Forward To?&lt;/h3>
&lt;p>For me these presentations look really interesting:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://perconaliveonline2020.sched.com/event/ePnR/mysql-ecosystem-on-arm?iframe=yes&amp;w=100%25&amp;sidebar=no&amp;bg=no" target="_blank" rel="noopener noreferrer">MySQL Ecosystem on ARM By Krunal Bauskar &amp; Mike Grayson&lt;/a>
&lt;ul>
&lt;li>I think ARM is a future for servers and historically I was always interested in MySQL implementations on non-x86 hardware.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://perconaliveonline2020.sched.com/event/ePp6/vitess-online-schema-migration-automation?iframe=yes&amp;w=100%25&amp;sidebar=no&amp;bg=no" target="_blank" rel="noopener noreferrer">Vitess Online Schema Migration Automation By Shlomi Noach &amp; Evgeniy Patlan&lt;/a>
&lt;ul>
&lt;li>Whatever Shlomi speaks about, it’s always interesting and useful!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>See the full conference agenda &lt;a href="https://www.percona.com/live/agenda" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p></content:encoded><author>Valeriy Kravchuk</author><category>valeriy.kravchuk</category><category>Events</category><category>MariaDB</category><category>perconalive</category><category>PLO-2020-10</category><media:thumbnail url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_ee2e8da0448380e1.jpg"/><media:content url="https://percona.community/blog/2020/10/DB-PLO-Blog-Image-2020-10-05_hu_e5cd6fdb7763e3c5.jpg" medium="image"/></item><item><title>Database Schema Management Via Liquibase</title><link>https://percona.community/blog/2020/10/01/database-schema-management-via-liquibase/</link><guid>https://percona.community/blog/2020/10/01/database-schema-management-via-liquibase/</guid><pubDate>Thu, 01 Oct 2020 22:30:04 UTC</pubDate><description>Creating the database for an application is simple and easy. However, database script management gets complicated in a hurry when you need to support multiple versions, work with multiple teams, and apply the same changes to multiple types of databases. </description><content:encoded>&lt;p>Creating the database for an application is simple and easy. However, database script management gets complicated in a hurry when you need to support multiple versions, work with multiple teams, and apply the same changes to multiple types of databases. &lt;/p>
&lt;p>One open-source tool that helps teams track, version, and deploy database schema changes is &lt;a href="https://www.liquibase.org" target="_blank" rel="noopener noreferrer">Liquibase&lt;/a>. It executes database scripts sequentially, allows for the automatic creation and execution of rollback scripts for failed updates, and provides an easy way to use the same scripts and apply them to different types of databases. To illustrate how Liquibase works, here’s an example using PostgreSQL.   &lt;/p>
&lt;h2 id="system-set-up">System set up:&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.liquibase.org/download" target="_blank" rel="noopener noreferrer">Download the latest version of Liquibase&lt;/a>. &lt;/li>
&lt;li>&lt;a href="https://jdbc.postgresql.org/download.html" target="_blank" rel="noopener noreferrer">Download the JDBC driver jar file for PostgreSQL&lt;/a>.&lt;/li>
&lt;li>Ensure the liquibase.bat file’s path is set to a location in the PATH System variable.&lt;/li>
&lt;/ul>
&lt;p>To test your connection, try running Liquibase with the JDBC driver located in the same directory as Liquibase:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">liquibase
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--driver=org.postgresql.Driver
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--classpath=postgresql-9.2-1002-jdbc4.jar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--url="jdbc:postgresql://&lt;IP OR HOSTNAME>:&lt;PORT>/&lt;DATABASE>"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--changeLogFile=db.changelog-1.0.xml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--username=&lt;POSTGRESQL USERNAME>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--password=&lt;POSTGRESQL PASSWORD>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="create-a-changelog-file">&lt;strong>Create a changelog file:&lt;/strong>&lt;/h2>
&lt;p>A Liquibase database &lt;em>changelog&lt;/em> is an XML, JSON, YAML, or SQL file that describes all changes that need to be performed to update the database. In most cases, you want to create one file for each release. Each file consists of one or more &lt;em>changesets&lt;/em>. (Note: The XML, JSON, and YAML definitions allow for abstraction, meaning that Liquibase is able to apply the same changes to any database. You can use database-specific SQL, as well.)&lt;/p>
&lt;p>Create a project folder called &lt;strong>LiquibasePostgres&lt;/strong>. In that folder, let’s create our database changelog. Create a new text file named &lt;strong>dbchangelog.xml&lt;/strong>. Drop this code into the file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;databaseChangeLog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.9.xsd">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/databaseChangeLog>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In the same &lt;strong>LiquibasePostgres&lt;/strong> folder, create a &lt;a href="https://docs.liquibase.com/workflows/liquibase-community/creating-config-properties.html" target="_blank" rel="noopener noreferrer">&lt;strong>liquibase.properties&lt;/strong> file&lt;/a>. Drop this code in with your username and password.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">changeLogFile: C:\\Users\\Administrator\\LiquibasePostgreSQL\\dbchangelog.xml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">url: jdbc:postgresql://localhost:5432/MYDATABASE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">username: postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">password: password
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">driver: org.postgresql.Driver
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">classpath: ../../Liquibase_Drivers/postgresql-42.2.8.jar&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now it’s time to write some code to generate your Postgres database. Let’s create our first &lt;em>changeset&lt;/em>. &lt;/p>
&lt;p>A changeset describes a set of changes that Liquibase executes. A best practice to keep in mind when using changesets is to have only one logical change per &lt;em>changeset&lt;/em>. Each &lt;em>changeset&lt;/em> is identified by the name of the author and an id. Liquibase stores this information together with the name of the &lt;em>changelog&lt;/em> file in a &lt;em>databasechangelog table&lt;/em> to keep track of your changes.&lt;/p>
&lt;p>In the &lt;strong>databasechangelog.xml&lt;/strong> file, add the following &lt;em>changeset&lt;/em>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;databaseChangeLog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.9.xsd">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;changeSet id="1" author="lucy">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;createTable tableName="department">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;column name="id" type="int">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;constraints primaryKey="true" nullable="false"/>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/column>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;column name="name" type="varchar(50)">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;constraints nullable="false"/>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/column>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;column name="active" type="boolean"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">defaultValueBoolean="true"/>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/createTable>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/changeSet>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/databaseChangeLog>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now that you understand the basics, you can easily start using Liquibase on any existing database. In this case, a Postgres database project you’re already working on. To achieve a starting point, you’ll need to generate a changelog based on your current database.&lt;/p>
&lt;p>Provide the connection information (described earlier) and use the &lt;a href="https://docs.liquibase.com/commands/community/generatechangelog.html" target="_blank" rel="noopener noreferrer">generateChangeLog command&lt;/a>. The generateChangeLog command generates a changelog file that contains all your objects (represented as changesets) and places the file in the same directory where the command was run.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">liquibase –driver=org.postgresql.Driver \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">–classpath=myFiles\\postgresql-9.4.1212.jre7.jar \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">–changeLogFile=myFiles/db.changelog-1.0.xml \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">–url=”jdbc:postgresql://localhost:5432/MYDATABASE” \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">–username=postgres \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">–password=postgres \\
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">generateChangeLog&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If you already have a database, generating the changelog is a lot easier (and a whole lot faster) than writing it yourself. &lt;a href="https://docs.liquibase.com/workflows/liquibase-community/existing-project.html" target="_blank" rel="noopener noreferrer">Here are some instructions on how to get started using an existing database&lt;/a>. Always review the generated changesets so that you can be sure everything looks as it should. &lt;/p>
&lt;h3 id="executing-liquibase">Executing Liquibase:&lt;/h3>
&lt;p>There are &lt;a href="https://www.liquibase.org/blog/3-ways-to-run-liquibase" target="_blank" rel="noopener noreferrer">3 primary ways to run Liquibase&lt;/a>. You can use command line or a Maven plugin to create the database as part of your build or deployment process. You can also use a Servlet, Spring, or CDI Listener to automatically create or update the database at application startup. &lt;a href="https://hub.docker.com/r/liquibase/liquibase" target="_blank" rel="noopener noreferrer">Liquibase also has an official Docker image&lt;/a>. &lt;/p>
&lt;h3 id="summing-up">Summing Up:&lt;/h3>
&lt;p>A version-based database migration process allows you to evolve your database together with your application code and to automatically apply database updates when you deploy a new release. In our next blog, we’ll walk you through how to combine Liquibase with a Percona extension to achieve zero-downtime schema changes.&lt;/p></content:encoded><author>Ronak Rahman</author><category>ronak.rahman</category><category>MySQL</category><category>PostgreSQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/10/Liquibase_hu_8cbc396f5acca75e.jpg"/><media:content url="https://percona.community/blog/2020/10/Liquibase_hu_3df949dcfe563d92.jpg" medium="image"/></item><item><title>How to Write Your Index in Tarantool</title><link>https://percona.community/blog/2020/09/28/how-to-write-your-index-in-tarantool/</link><guid>https://percona.community/blog/2020/09/28/how-to-write-your-index-in-tarantool/</guid><pubDate>Mon, 28 Sep 2020 17:23:02 UTC</pubDate><description>Tarantool is an application server and a database. The server part is written in C, and the user is provided with a Lua interface to use it. Then, Tarantool is an open-source product with its source code in open access, and anyone can develop and distribute Tarantool-based software freely.</description><content:encoded>&lt;p>Tarantool is an application server and a database. The server part is written in C, and the user is provided with a Lua interface to use it. Then, &lt;a href="https://www.tarantool.io/en/developers/" target="_blank" rel="noopener noreferrer">Tarantool&lt;/a> is an open-source product with its source code in open access, and anyone can develop and distribute Tarantool-based software freely.&lt;/p>
&lt;p>Today, I’m going to tell you about an attempt to write my own data structure for the search (the Z-order curve) and build it into the Tarantool ecosystem.&lt;/p>
&lt;p>I work as a developer in the Tarantool Solution Team. I don’t create &lt;a href="https://www.tarantool.io/en/developers/" target="_blank" rel="noopener noreferrer">Tarantool&lt;/a> but use it extensively. For me, this is an experiment — an attempt to figure out how Tarantool works at a lower level.&lt;/p>
&lt;h3 id="what-is-tarantool-and-where-does-it-store-data">What is Tarantool, and where does it store data?&lt;/h3>
&lt;p>&lt;a href="https://www.tarantool.io/en/developers/" target="_blank" rel="noopener noreferrer">Tarantool&lt;/a> is known as an in-memory database (though we have an engine for disk storage too). Engine memtx allows you to store all your data in random access memory and satisfies all ACID principles at the same time.&lt;/p>
&lt;p>The equivalent of relational tables in Tarantool is space where tuples are stored. Unlike relational tables, tuples in space can generally have an optional length. Physically, they are stored in a search data structure, with the search key — primary key — is always unique.&lt;/p>
&lt;p>Supplementary structures can be built too, i.e. secondary indexes that store only a pointer to a tuple. A secondary index can be not unique, but this is only observable behavior visible to the user. Any index that is not unique is added with primary index fields by default. This way, the stability of tuple sorting inside the index is ensured.&lt;/p>
&lt;p>Tarantool supports different types of indexes:&lt;/p>
&lt;ul>
&lt;li>Firstly, it is B-Tree (B+*-Tree, to be exact). Quite a lot has been written about the structure of [&lt;a href="https://habr.com/ru/company/mailru/blog/505880/%23B-Tree" target="_blank" rel="noopener noreferrer">B-Trees&lt;/a>]. I would only note that data is stored in a sorted form, making it possible to search by indexed key prefix.&lt;/li>
&lt;li>Index based on a hash table. It will suit you if you don’t need interval selections. The access is by full key. Unlike B-Tree, the time of access to an element is constant, not logarithmical. This index is always unique.&lt;/li>
&lt;li>R-Tree. This type of index is more specialized. It is intended for storing “multidimensional” data, i.e. geographical coordinates. It supports indexing for fields of only one type: array. This is the array of our false coordinates which are ordinary floating-point numbers. It enables search for points located both within and outside a specified border, as well as the nearest neighbors.&lt;/li>
&lt;li>Bitset. It is intended for storing bit arrays in spaces and fulfilling requests using bitmasks.&lt;/li>
&lt;/ul>
&lt;h3 id="z-order-curve-or-morton-curve">&lt;strong>Z-order curve, or Morton curve&lt;/strong>&lt;/h3>
&lt;p>Where the idea to write my own index, a rather exotic one, came from?&lt;/p>
&lt;p>Once I stumbled upon Amazon articles [&lt;a href="https://habr.com/ru/company/mailru/blog/505880/%231" target="_blank" rel="noopener noreferrer">1, 2&lt;/a>] telling about the Z-order curve, or Morton curve, structure. This is a scheme for arranging “multidimensional” data inside a flat structure (Z-order curve), which is then fitted into B-Tree. This should help avoid total data scanning. Generally, information about any object having a set of characteristics can be considered multidimensional data. For example, an individual’s height, weight, shoe size, etc. R-Tree is used for this purpose in most databases.&lt;/p>
&lt;p>A little bit about how the Z-order curve works.
&lt;figure>&lt;img width="567" height="558" sizes="(max-width: 567px) 100vw, 567px" srcset="https://percona.community/blog/2020/09/image3_hu_4973dcaa6b8eae63.webp 480w, https://percona.community/blog/2020/09/image3_hu_85f4c5e414da8e2f.webp 567w" src="https://percona.community/blog/2020/09/image3_hu_85f4c5e414da8e2f.webp" alt="How to Write Your Index in Tarantool" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The Z-order curve, or Morton curve, is obtained by interleaving bits of a point’s space coordinates. Z-addresses obtained this way have the property of locality. Points that were adjacent in multidimensional space would normally be located next to each other in projection to a flat line as well.&lt;/p>
&lt;p>Schematically, interleaving looks like this:
&lt;figure>&lt;img width="1000" height="427" sizes="(max-width: 1000px) 100vw, 1000px" srcset="https://percona.community/blog/2020/09/image5_hu_f11f39dd0f4bdd8.webp 768w, https://percona.community/blog/2020/09/image5_hu_ac53ba79b121f0a3.webp 480w, https://percona.community/blog/2020/09/image5_hu_768270d95172c351.webp 1000w" src="https://percona.community/blog/2020/09/image5_hu_768270d95172c351.webp" alt="How to Write Your Index in Tarantool 2" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>How does it work? We delimit a region in space — hypercube (rectangle, if in two-dimensional space) — using two points located on a diagonal. They are projected on two points on a straight line. And then we see an unpleasant side effect: some points are outside the delimiting rectangle.
&lt;figure>&lt;img width="567" height="558" sizes="(max-width: 567px) 100vw, 567px" srcset="https://percona.community/blog/2020/09/image4_hu_f113ab77af038943.webp 480w, https://percona.community/blog/2020/09/image4_hu_dae21c7bc308190c.webp 567w" src="https://percona.community/blog/2020/09/image4_hu_dae21c7bc308190c.webp" alt="How to Write Your Index in Tarantool 3" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>That is, we can’t just iterate with this curve. Fortunately, we can “jump” back to the search area using a special algorithm when we exceed the limits. As soon as we go beyond the end point of the curve (let’s call it upper_bound), the search is over.&lt;/p>
&lt;p>In the case of B-Tree, we would have a set of intervals, and sequential scanning of B-Tree would start upon such request. This does take quite a lot of time in the case of a large data set.&lt;/p>
&lt;p>There were other publications about this curve that heated up my interest. For example, those about how it was integrated in TransBase [&lt;a href="https://habr.com/ru/company/mailru/blog/505880/%233" target="_blank" rel="noopener noreferrer">3&lt;/a>], a proprietary DB. However, I couldn’t find any open-source implementation of this structure.&lt;/p>
&lt;p>B-Tree has an advantage over R-Tree as the basis of this curve: better filling and compact. Drawbacks: Most of the algorithms used are limited by processor. I decided to run a comparison using Tarantool: the focus was on read/insert speed, as well as memory consumption.&lt;/p>
&lt;h3 id="what-was-required-for-building-in-tarantool">What was required for building in Tarantool?&lt;/h3>
&lt;p>I found simple implementations of this structure for 2-3 dimensions, but I wasn’t interested in small dimensions as I was keen to compare performance with R-Tree index. I chose the same maximum dimensions as with R-Tree in Tarantool, i.e. 20. To do that, I needed a bit array with support for some primitive operations: bit retrieval/alteration, shift, and OR/AND logical operations.&lt;/p>
&lt;p>First, I was about to use a borrowed open source-implementation, but soon I realized that I didn’t need a general-use bit array: key length is always equal to 64, so some operations get significantly simplified. I wrote my own implementation. Instead of system memory allocation functions, I began to use special allocators implemented in Tarantool [&lt;a href="https://habr.com/ru/company/mailru/blog/505880/%234" target="_blank" rel="noopener noreferrer">4&lt;/a>].&lt;/p>
&lt;p>The heart of index is a set of algorithms for using Z-order curve: computation of Z-address (using special lookup tables), check of whether a Z-address belongs to the search area, and detection of the first entry in the search area starting from a specified point. Research publications describing these algorithms can be found on the Web. I implemented, debugged and optimized them. My plan was to store Z-addresses inside already implemented B-Tree used for the TREE index.&lt;/p>
&lt;h3 id="how-data-handling-is-arranged">How data handling is arranged?&lt;/h3>
&lt;p>In the most general case, we have tuple. This is an array of data in message pack format. Ideally, it’s enough to isolate indexed fields, interleave their bits and insert address with a pointer to tuple inside B-Tree.&lt;/p>
&lt;p>However, it all would be so easy if we dealt only with the type unsigned, where sorted bit presentations of numbers would correspond to sorted numbers in natural presentation. Signed whole numbers have one set of presentation rules, and floating-point numbers have another. It all had to be combined. Since we store Z-address separately from the data itself, it is possible to make any kind of transformation with our keys, the main thing is to keep the sorting order. This can be done through fairly simple bit manipulations. For instance, for signed whole numbers, the lead byte could just be inverted. For other types, there are similar transformations, though a bit more complex.&lt;/p>
&lt;p>All numerical types fit into 8 bytes, so the size of the resulting key will be N * 8 bytes, where N is the dimensions of our space. What to do with strings?&lt;/p>
&lt;p>A quite common situation in work with strings is the prefix search. The first 8 bytes of a string can be used as a key. If the string is shorter, it can be added with zeros. Support for strings imposes a fundamental limitation on our index: we lose uniqueness. Even when strings differ in the ninth byte, they still will look the same for the system.&lt;/p>
&lt;p>Let’s look at the code.&lt;/p>
&lt;p>API of an index comprises a set of methods. Let’s consider only the main ones: search and insert operations.&lt;/p>
&lt;p>get — element search by the full key. It works only for unique indexes. Our index cannot be unique, so the function is replaced by a special generic version that returns the “Unsupported index feature" error.&lt;/p>
&lt;p>replace — element insertion. Let’s review this in more detail.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">static int
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">memtx_zcurve_index_replace(struct index *base, struct tuple *old_tuple,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct tuple *new_tuple, enum dup_replace_mode mode,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct tuple **result)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (void)mode;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_zcurve_index *index = (struct memtx_zcurve_index *)base;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (new_tuple) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_zcurve_data new_data;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new_data.tuple = new_tuple;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> new_data.z_address = extract_zaddress(new_tuple,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;index->bit_array_pool, index);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_zcurve_data dup_data;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dup_data.tuple = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dup_data.z_address = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int tree_res = memtx_zcurve_insert(&amp;index->tree, new_data,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;dup_data);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (tree_res) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "memtx_zcurve_index", "replace");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return -1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (dup_data.tuple != NULL) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *result = dup_data.tuple;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> z_value_free(&amp;index->bit_array_pool, dup_data.z_address);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (old_tuple) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_zcurve_data old_data, deleted_value;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> old_data.tuple = old_tuple;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> old_data.z_address = extract_zaddress(old_tuple,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;index->bit_array_pool, index);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> memtx_zcurve_delete_value(&amp;index->tree, old_data, &amp;deleted_value);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> z_value_free(&amp;index->bit_array_pool, old_data.z_address);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> z_value_free(&amp;index->bit_array_pool, deleted_value.z_address);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *result = old_tuple;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What should be noted here?&lt;/p>
&lt;p>From the index perspective, there is no update, delete, insert, and replace operations. Their whole logic is performed in this method, receiving the old and the new tuples, as well as mode, i.e. information on whether the index is unique or not. Our index cannot be unique, so no additional check is required, and tuple can be inserted at once.&lt;/p>
&lt;p>Methods memtx_zcurve_insert and memtx_zcurve_delete_value are methods of the B-Tree which has been already implemented in Tarantool and are used in common TREE index. Unlike a common TREE, we store not just tuple but Z-address as well — interleaved bits of indexed parts. Function extract_zadress is responsible for this.&lt;/p>
&lt;p>Method create_iterator: from Lua, we invoke this method in case of select and pairs.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">static struct iterator *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">memtx_zcurve_index_create_iterator(struct index *base, enum iterator_type type,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const char *key, uint32_t part_count)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_zcurve_index *index = (struct memtx_zcurve_index *)base;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> assert(part_count == 0 || key != NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (type != ITER_EQ &amp;&amp; type != ITER_ALL &amp;&amp; type != ITER_GE) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> diag_set(UnsupportedIndexFeature, base->def,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "requested iterator type");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uint8_t index_dim = base->def->key_def->part_count;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (part_count == 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * If no key is specified, downgrade equality
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * iterators to a full range.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type = ITER_GE;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else if (index_dim * 2 == part_count
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;&amp; type != ITER_ALL) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * If part_count is twice greater than key_def.part_count
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * set iterator to a range query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type = ITER_GE;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> struct tree_iterator *it = mempool_alloc(&amp;memtx->zcurve_iterator_pool);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (it == NULL) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> diag_set(OutOfMemory, sizeof(struct tree_iterator),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> "memtx_zcurve_index", "iterator");
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> iterator_create(&amp;it->base, base);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->pool = &amp;memtx->zcurve_iterator_pool;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->base.next = tree_iterator_start;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->base.free = tree_iterator_free;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->type = type;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (part_count == 0 || type == ITER_ALL) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->lower_bound = zeros(&amp;index->bit_array_pool, index_dim);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->upper_bound = ones(&amp;index->bit_array_pool, index_dim);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else if (type == ITER_EQ) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->lower_bound = mp_decode_key(&amp;index->bit_array_pool,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key, index_dim, index);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->upper_bound = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else if (base->def->key_def->part_count == part_count) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->lower_bound = mp_decode_key(&amp;index->bit_array_pool,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key, index_dim, index);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->upper_bound = ones(&amp;index->bit_array_pool, index_dim);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else if (base->def->key_def->part_count * 2 == part_count) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->lower_bound = z_value_create(&amp;index->bit_array_pool, index_dim);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->upper_bound = z_value_create(&amp;index->bit_array_pool, index_dim);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mp_decode_part(key, part_count, index, it->lower_bound, it->upper_bound);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unreachable();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->tree_iterator = memtx_zcurve_invalid_iterator();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->current.tuple = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> it->current.z_address = NULL;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return (struct iterator *)it;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Depending on the provided key, we compute the lower and the upper request boundaries. However, this iterator points at nothing as yet. There are several types of iterators. In this case, it’s ALL — obtain all elements; EQ — obtain all elements whose Z-address matches the delivered one; and GE — a selection of elements in a hypercube.&lt;/p>
&lt;p>destroy — delete index. When the index is secondary, the function frees up memory which was allocated to the search structure. And when the index is primary, it physically deletes stored tuples.&lt;/p>
&lt;p>The whole code is available at &lt;a href="https://github.com/olegrok/tarantool/tree/z-order-curve-index" target="_blank" rel="noopener noreferrer">https://github.com/olegrok/tarantool/tree/z-order-curve-index&lt;/a>&lt;/p>
&lt;p>Let’s see what we have as a result and draw conclusions.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">space = box.schema.space.create('myspace', { engine = 'memtx' })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pk = space:create_index('primary', { type = 'tree', parts = {{1, 'unsigned'}}, unique = true})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sk = space:create_index('secondary', { type = 'zcurve', parts = {{2, 'unsigned'}, {3, 'unsigned'}}})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for i=0,5 do for j=0,5 do space:insert{i * 6 + j, i, j} end end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- returns all tuples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pk:select{}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- (2 &lt;= x &lt;= 3) and (3 &lt;= y &lt;= 5)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sk:select{2, 3, 3, 5}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- — [15, 2, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [21, 3, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [16, 2, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [22, 3, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [17, 2, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [23, 3, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">…
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- (x == 2) and (y == 3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sk:select{2, 3}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- — [15, 2, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- (2 &lt;= x &lt;= 3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sk:select({2, 3, box.NULL, box.NULL})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- — [12, 2, 0]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [18, 3, 0]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [13, 2, 1]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [19, 3, 1]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [14, 2, 2]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [20, 3, 2]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [15, 2, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [21, 3, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [16, 2, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [22, 3, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [17, 2, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [23, 3, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- (x >= 2) and (y >= 3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sk:select({2, box.NULL, 3, box.NULL})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- — [15, 2, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [21, 3, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [27, 4, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [33, 5, 3]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [16, 2, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [22, 3, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [17, 2, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [23, 3, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [28, 4, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [34, 5, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [29, 4, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - [35, 5, 5]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="what-about-performance">What about performance?&lt;/h3>
&lt;p>Not so great, as it turned out.&lt;/p>
&lt;p>Starting from some point, the Z-order curve starts losing data access speed considerably. As shown by perf top, most of the time was spent on checking whether a point belonged to the search area, and on computing the next point to jump to. Both operations have linear complexity depending on the key length, i.e. length grows along with growing dimensions.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="692" height="620" sizes="(max-width: 692px) 100vw, 692px" srcset="https://percona.community/blog/2020/09/1_hu_531405495d8a6e68.webp 480w, https://percona.community/blog/2020/09/1_hu_95723d4b0d8df2d2.webp 692w" src="https://percona.community/blog/2020/09/1_hu_95723d4b0d8df2d2.webp" alt="How to Write Your Index in Tarantool Cover 15" loading="lazy" />&lt;/figure>
[data access time in seconds / Dimensions]&lt;/p>
&lt;p>Good news: memory consumption is 2–3 times lower and insertion is slightly faster than with R-Tree. But the measurements were performed with disengaged WAL. In a production environment, disengaged WAL can lead to data loss in case of a fault. On top of that, although WAL writing uses a batch approach, it is still writing to disk, which is thousands of times slower than random access memory operation.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="703" height="623" sizes="(max-width: 703px) 100vw, 703px" srcset="https://percona.community/blog/2020/09/2_hu_df86749490c36a02.webp 480w, https://percona.community/blog/2020/09/2_hu_69da0a5e27d4cd87.webp 703w" src="https://percona.community/blog/2020/09/2_hu_69da0a5e27d4cd87.webp" alt="How to Write Your Index in Tarantool Cover 14" loading="lazy" />&lt;/figure>
[Required memory in megabytes / Dimensions]&lt;/p>
&lt;p>
&lt;figure>&lt;img width="685" height="620" sizes="(max-width: 685px) 100vw, 685px" srcset="https://percona.community/blog/2020/09/3_hu_b081e10fd81068ab.webp 480w, https://percona.community/blog/2020/09/3_hu_60a7cc27db7a25df.webp 685w" src="https://percona.community/blog/2020/09/3_hu_60a7cc27db7a25df.webp" alt="How to Write Your Index in Tarantool Cover 12" loading="lazy" />&lt;/figure>
[Insertion time in seconds / Dimensions]&lt;/p>
&lt;p>It is also interesting to compare to B-Tree. The curve turned out to be faster than total scanning and checking each point for belonging to the specified area. This is true even though the check is more light-weight than the Z-order curve where it all comes down to bit-wise comparison. Values on the graph differ by order from R-Tree: the test was slightly modified.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="690" height="620" sizes="(max-width: 690px) 100vw, 690px" srcset="https://percona.community/blog/2020/09/4_hu_ea87df5b0e847eef.webp 480w, https://percona.community/blog/2020/09/4_hu_20ddaf2ac7c889e8.webp 690w" src="https://percona.community/blog/2020/09/4_hu_20ddaf2ac7c889e8.webp" alt="How to Write Your Index in Tarantool Cover 11" loading="lazy" />&lt;/figure>
[Data access time in seconds / Dimensions]&lt;/p>
&lt;p>For the test, I generated a set of points and compared time length for request using Z-curve and for ordinary scanning.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>The in-memory world didn’t bring out the best in this structure; however, it has some advantages:&lt;/p>
&lt;ul>
&lt;li>Takes less space.&lt;/li>
&lt;li>Typed, unlike R-Tree (relevant only for Tarantool).&lt;/li>
&lt;li>It makes sense to give it a closer look when only B-Tree is available, and multidimensional requests are required to be made (not relevant for Tarantool).&lt;/li>
&lt;/ul>
&lt;p>It was an exciting experiment. Although, the solution I proposed here would hardly become a part of Tarantool.&lt;/p>
&lt;h2 id="sources">Sources:&lt;/h2>
&lt;p>[B-Tree]:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://habr.com/ru/company/postgrespro/blog/330544/" target="_blank" rel="noopener noreferrer">Indexes PostgreSQL — 4 / Postgres Professional Blog / Habr&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://habr.com/ru/post/114154/" target="_blank" rel="noopener noreferrer">B-tree / Habr&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://habr.com/ru/company/otus/blog/459216/" target="_blank" rel="noopener noreferrer">B-Tree data structure / OTUS Blog. Online education / Habr&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>[ZcurvePostgres]:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://habr.com/ru/post/319096/" target="_blank" rel="noopener noreferrer">About Z-оrder and R-Tree / Habr&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://habr.com/ru/post/319810/" target="_blank" rel="noopener noreferrer">Z-order vs R-tree, continued / Habr&lt;/a>&lt;/li>
&lt;/ul>
&lt;p> &lt;/p>
&lt;ul>
&lt;li>[1] &lt;a href="https://aws.amazon.com/ru/blogs/database/z-order-indexing-for-multifaceted-queries-in-amazon-dynamodb-part-1/" target="_blank" rel="noopener noreferrer">Z-Order Indexing for Multifaceted Queries in Amazon DynamoDB: Part 1 | AWS Database Blog&lt;/a>&lt;/li>
&lt;li>[2] &lt;a href="https://aws.amazon.com/ru/blogs/database/z-order-indexing-for-multifaceted-queries-in-amazon-dynamodb-part-2/" target="_blank" rel="noopener noreferrer">Z-order indexing for multifaceted queries in Amazon DynamoDB: Part 2 | AWS Database Blog&lt;/a>&lt;/li>
&lt;li>[3] &lt;a href="http://www.cs.bu.edu/fac/gkollios/cs591/RMF+00.pdf" target="_blank" rel="noopener noreferrer">Integrating the UB-Tree into a Database System Kernel&lt;/a>&lt;/li>
&lt;li>[4] &lt;a href="https://github.com/tarantool/small" target="_blank" rel="noopener noreferrer">https://github.com/tarantool/small&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Oleg Babin</author><category>Databases</category><category>Information</category><category>Intermediate Level</category><category>Open Source Databases</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/09/tarantool_cover_hu_b846d1c773d52cb7.jpg"/><media:content url="https://percona.community/blog/2020/09/tarantool_cover_hu_2bff6d0ead2b483.jpg" medium="image"/></item><item><title>Google Summer of Code Refactor PMM Framework Project with Percona</title><link>https://percona.community/blog/2020/09/07/google-summer-of-code-refactor-pmm-framework-project-with-percona/</link><guid>https://percona.community/blog/2020/09/07/google-summer-of-code-refactor-pmm-framework-project-with-percona/</guid><pubDate>Mon, 07 Sep 2020 11:08:19 UTC</pubDate><description>I am Meet Patel, 2nd year undergraduate at DAIICT, Gandhinagar, India; pursuing a bachelor’s degree in Information and Communication Technology with a minor in Computational Science.</description><content:encoded>&lt;p>I am &lt;strong>Meet Patel&lt;/strong>, 2nd year undergraduate at DAIICT, Gandhinagar, India; pursuing a bachelor’s degree in Information and Communication Technology with a minor in Computational Science.&lt;/p>
&lt;p>I am proud to be selected for the &lt;strong>Google Summer of Code&lt;/strong> program under an open source organization as big and impactful as &lt;strong>Percona&lt;/strong>. As we head towards the end of this amazing program, I’ll try to share a general overview of what and how all of it has been implemented.&lt;/p>
&lt;h2 id="about-the-project">About the project&lt;/h2>
&lt;p>PMM-Framework is a shell based tool to quickly deploy Percona Monitoring and Management, add different database clients to it and load test them; all fully automated. It can automatically download and install through Tarball installers and Docker images for the specific version provided. It incorporates usage of tools like DB Deployer to deploy MySQL databases. Other supported DBs by PMM-Framework include Percona Server, MongoDB, Percona Server for MongoDB, PostgreSQL, MariaDB and PXC. It can also be used to wipe all the PMM configuration after tests are done.&lt;/p>
&lt;p>The main objective of the project was to make bug fixes, refactor the framework, add stability to it and make it more robust and useful. In the first half of the project timeline, I worked on implementing the above tasks and tested PMM using the PMM-Framework. Being a shell based tool, PMM-Framework had a slightly steep learning curve for newcomers. So given the mentors’ suggestions, I made a user friendly CLI tool from scratch, namely PMM-Framework-CLI, that would query the user and execute PMM-Framework on the machine, or inside a VagrantBox.&lt;/p>
&lt;p>You can check out the quick demo here:  &lt;a href="https://youtu.be/qPXlTMrsBcU" target="_blank" rel="noopener noreferrer">https://youtu.be/qPXlTMrsBcU&lt;/a> You can check out my contributions to PMM-Framework at &lt;a href="https://github.com/percona/pmm-qa/tree/GSOC-2020" target="_blank" rel="noopener noreferrer">GSoC Project Branch&lt;/a>. The source code to the PMM-Framework-CLI tool can be found &lt;a href="https://github.com/Percona-Lab/pmm-framework-cli" target="_blank" rel="noopener noreferrer">here&lt;/a>. This tool is soon to be published on NPM so that everyone can quickly start using it through the NPM repository.&lt;/p>
&lt;h2 id="challenges-faced">Challenges faced&lt;/h2>
&lt;p>There would be many unforeseen challenges regardless of the project, overcoming them teaches you a lot. The first challenge that I faced in this project was to understand how everything was working in PMM. I went through every documentation that I could find to understand PMM architecture. Working with shell scripts of this size and debugging them was also a challenge. Due to Covid-19 my university exams timelines were uncertain, mentors also helped me manage that. I didn’t have a lot of prior knowledge about many Linux, Database and Networking concepts, learning which only has added to my skills.&lt;/p>
&lt;h2 id="experiences">Experiences&lt;/h2>
&lt;p>It has been a great learning experience as well. I have got a really great opportunity to experiment and work hands on numerous tools and technologies in such a short timespan. To list some of them:&lt;/p>
&lt;ul>
&lt;li>Docker&lt;/li>
&lt;li>Bash Scripting&lt;/li>
&lt;li>NodeJS (and publishing package to NPM)&lt;/li>
&lt;li>Linux, Networking, Databases&lt;/li>
&lt;li>Ansible&lt;/li>
&lt;li>SSH&lt;/li>
&lt;li>Jenkins Pipelines&lt;/li>
&lt;li>Percona Monitoring and Management (of course!)&lt;/li>
&lt;/ul>
&lt;p>Apart from these, the common and biggest advantage of any Google Summer of Code project is that you get to understand a huge codebase that you wouldn’t otherwise. You also get exposed to the best coding practices, development workflows, issues management, time management to name a few. Apart from this, although not part of GSoC but related to the work, I wrote an article about Encryption in SSH/HTTPS that has been trending on the Cybersecurity domain of Medium. The article can be read here: &lt;a href="https://medium.com/code-dementia/demystifying-secure-in-ssh-tls-https-ad7473106c6a" target="_blank" rel="noopener noreferrer">https://medium.com/code-dementia/demystifying-secure-in-ssh-tls-https-ad7473106c6a&lt;/a>&lt;/p>
&lt;p>The part I loved most is the exposure that I got, let alone the learning. The mentors have been extremely friendly and supportive about everything. I also got to improve on my communication skills because of my regular interaction with the mentors. This project has for sure been a great addition to my résumé. I’m happy to announce that this also helped me secure a summer internship at Goldman Sachs for the next summer!  Being a student, learning directly from people having 10x the experience you have not only teaches you well but also prepares for how the team work really happens.&lt;/p>
&lt;p>Overall, it has been an absolutely amazing experience working with Percona and a special thanks to the mentors &lt;strong>Puneet Kala, Nailya Kutlubaeva, Vasyl Yurkovych&lt;/strong> of Percona for guiding me throughout.&lt;/p></content:encoded><author>Meet Patel</author><category>Entry Level</category><category>Google Summer of Code</category><category>GSoC</category><category>MySQL</category><category>MySQL</category><category>Open Source Databases</category><category>PMM</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/09/Screenshot-2020-09-07-at-14.46.59_hu_ad54675b8ef67ddf.jpg"/><media:content url="https://percona.community/blog/2020/09/Screenshot-2020-09-07-at-14.46.59_hu_ca47f506bcbe7cac.jpg" medium="image"/></item><item><title>Two weeks to MariaDB Server Fest</title><link>https://percona.community/blog/2020/09/04/two-weeks-to-mariadb-server-fest/</link><guid>https://percona.community/blog/2020/09/04/two-weeks-to-mariadb-server-fest/</guid><pubDate>Fri, 04 Sep 2020 09:34:53 UTC</pubDate><description>There is still time to register for the MariaDB Server Fest 2020!</description><content:encoded>&lt;p>There is still time to &lt;a href="https://mariadb.org/fest-registration/" target="_blank" rel="noopener noreferrer">register&lt;/a> for the MariaDB Server Fest 2020!&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="720" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/09/mariadb_fest_video_hu_c94346fb16df1f6f.webp 768w, https://percona.community/blog/2020/09/mariadb_fest_video_hu_8338fc8abb95c716.webp 480w, https://percona.community/blog/2020/09/mariadb_fest_video_hu_54980ec19ebf48a5.webp 1280w" src="https://percona.community/blog/2020/09/mariadb_fest_video_hu_54980ec19ebf48a5.webp" alt="MariaDB Fest 2020" loading="lazy" />&lt;/figure> MariaDB Fest 2020[/caption]&lt;/p>
&lt;p>Our Fest is the opportunity to have live interactions with the key players on MariaDB Server: the developers of MariaDB Server, the service providers, the experts, the system integrators, and – perhaps most importantly – your fellow users!&lt;/p>
&lt;p>Interactivity happens all the time, with the presenters being cloned and available for answering questions throughout the presentation. This is because the presentations (including voice, a talking head, and the slide decks) are pre-recorded, freeing up the presenter’s attention to be fully devoted to the audience. Multithreading!&lt;/p>
&lt;p>Sessions are listed in full on the &lt;a href="https://mariadb.org/fest2020-sessions" target="_blank" rel="noopener noreferrer">web&lt;/a>, with the exact timing for the three virtual locations still being fine-tuned. Turn in to listen to 30 presenters from eg. Supermetrics, MariaDB Corporation, Percona, Microsoft, Galera, Tencent, Bilibili and MariaDB Foundation.&lt;/p>
&lt;p>Timing is during your day-time, and spread out across three days, five hours a day, so you can still get most of your normal job done.&lt;/p>
&lt;p>On Monday-Wednesday 14-16 Sep 2020 we have the Paris conference, on Tuesday-Thursday 15-17 Sep 2020 we have the New York conference, and on Friday-Sunday 18-20 Sep 2020 the Beijing conference. Exact agendas vary slightly between the locations, to cater to the sleeping patterns of the presenters from other time zones.&lt;/p>
&lt;p>Talk to you in less than two weeks!&lt;/p>
&lt;p>Links:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Registration: &lt;a href="https://mariadb.org/fest-registration/" target="_blank" rel="noopener noreferrer">https://mariadb.org/fest-registration/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Session list: &lt;a href="https://mariadb.org/fest2020-sessions/" target="_blank" rel="noopener noreferrer">https://mariadb.org/fest2020-sessions/&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></content:encoded><author>Kaj Arnö</author><category>kaj.arno</category><category>Events</category><category>MariaDB</category><category>MariaDB</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><media:thumbnail url="https://percona.community/blog/2020/09/mariadb_fest_video_hu_c1795516dda166aa.jpeg"/><media:content url="https://percona.community/blog/2020/09/mariadb_fest_video_hu_49e15c79741cdaae.jpeg" medium="image"/></item><item><title>IoT Performance Bottlenecks &amp; Open Source Databases</title><link>https://percona.community/blog/2020/09/03/iot-performance-bottlenecks-and-open-source-databases/</link><guid>https://percona.community/blog/2020/09/03/iot-performance-bottlenecks-and-open-source-databases/</guid><pubDate>Thu, 03 Sep 2020 17:35:59 UTC</pubDate><description>The Internet of Things (IoT), in essence, is all about everyday devices that are readable, recognizable, trackable, and/or controllable via the Internet, regardless of the communication means — RFID, wireless LAN, and so on. The total installed base of IoT connected devices is projected to amount to 21.5 billion units worldwide by 2025. Thanks to IoT, the proliferation of data can be quite daunting. Hence, businesses should effectively organize and work with this enormous amount of valuable data.</description><content:encoded>&lt;p>The Internet of Things (IoT), in essence, is all about everyday devices that are readable, recognizable, trackable, and/or controllable via the Internet, regardless of the communication means — RFID, wireless LAN, and so on. The total installed base of IoT connected devices is projected to amount to &lt;a href="https://www.statista.com/statistics/1101442/iot-number-of-connected-devices-worldwide/" target="_blank" rel="noopener noreferrer">21.5 billion units worldwide by 2025&lt;/a>. Thanks to IoT, the proliferation of data can be quite daunting. Hence, businesses should effectively organize and work with this enormous amount of valuable data.&lt;/p>
&lt;p>Databases play a pivotal role in enabling enterprises to make the most of IoT by facilitating proper organization, storage, and manipulation of data. IoT applications typically make use of both relational and non-relational (aka NoSQL) types of databases. While the selection of the type of database is made based on the type of application, in most cases, a mix of both types is used.  However, picking the most efficient database for a particular IoT application can be tricky. There are so many parameters to consider, such as scalability, availability, data handling ability, processing speed, schema flexibility, integration with required analytical tools, security, and cost.&lt;/p>
&lt;h2 id="key-business-drivers-of-iot">&lt;strong>Key Business Drivers of IoT&lt;/strong>&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="2560" height="2175" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/08/iot1-scaled_hu_25fa11d9681ca80a.webp 768w, https://percona.community/blog/2020/08/iot1-scaled_hu_1b09ee186d9bb9cf.webp 480w, https://percona.community/blog/2020/08/iot1-scaled_hu_960a88b9ab948ad3.webp 1400w" src="https://percona.community/blog/2020/08/iot1-scaled_hu_960a88b9ab948ad3.webp" alt=" " loading="lazy" />&lt;/figure> &lt;a href="https://www.freepik.com/vectors/coffee" target="_blank" rel="noopener noreferrer">Coffee vector created by macrovector - www.freepik.com&lt;/a>&lt;/p>
&lt;p>In the implementation of IoT applications for enterprises, there’s a need for flexibility in processing the data at the edge and to synchronize the data between edge servers and the cloud. No single commercial database can fulfill all such needs of an organization.  IoT is the basis of DevOps, agile software, and other development methodologies. Plus, thousands of developers are coming up with innovative IoT products, exponentially increasing the number of new devices and sources of data. Hence, the faster they come up with an idea and develop it the better it is. An &lt;a href="https://www.percona.com/blog/2020/04/30/the-state-of-the-open-source-database-industry-in-2020-part-four/" target="_blank" rel="noopener noreferrer">open-source database&lt;/a> is a cost-effective and versatile option for business IoT applications:&lt;/p>
&lt;ul>
&lt;li>The database can bring together data from all the devices and sensors, allowing developers to be creative and develop internal tools, standalone products, or components of bigger systems.&lt;/li>
&lt;li>It offers several tool kits and libraries for the faster development of IoT devices while keeping the risk and costs under control. Further, open-source hardware like Arduino and Raspberry Pi can help turn up several IoT devices, from home security to health monitors.&lt;/li>
&lt;li>An open source database lowers the cost of the device. That’s because it offers a variety of accessible open source databases such as MongoDB, Cassandra, and MySQL/MariaDB that help manage data at a lower cost. This allows enterprises to experiment with various solutions that would otherwise be ignored because of the high cost of licenses for development tools and software components.&lt;/li>
&lt;li>It makes it easy for developers to prototype IoT devices and convert them into full-fledged products like aquariums and thermostats. As open source is accessible to all, developers just need to tap a few pre-existing open source libraries, customize it as per their needs, and contribute it back to the community.&lt;/li>
&lt;/ul>
&lt;p>For instance, several startups are building wearables that can sense environmental factors, such as air composition and microbial content, and matching it with public databases to warn the wearer about traces of a specific pathogen in real-time. This is feasible because they are leveraging existing open source libraries and tools.  &lt;/p>
&lt;h2 id="iot-database-architecture">&lt;strong>IoT Database Architecture&lt;/strong>&lt;/h2>
&lt;p>In a typical IoT architecture, hundreds to thousands of sensors and actuators are connected with the edge server, and the enterprise IoT solution collects data from all these devices continuously.  Cloud MQTT, Apache Kafka, and Rest Service components are used to &lt;a href="https://dzone.com/articles/iot-and-event-streaming-at-scale-with-kafka-and-mq" target="_blank" rel="noopener noreferrer">ingest the IoT data streams&lt;/a> from the devices to the database. Next, edge analytics performs the translation, aggregation, and filtering of the incoming data, which allows real-time decision making at the edge. &lt;/p>
&lt;p>The database must support high-speed read and write operations with sub-millisecond latency. It helps in performing complex analytical operations on the data from the edge server. The database then communicates commands to the IoT devices and stores the data for as long as required.  Simply put, the whole IoT implementation is centered around the idea of data collection/insertion through sensors and sending instructions back to those devices. And so, open-source software like databases and even VPNs (check out &lt;a href="https://vpn-review.com/" target="_blank" rel="noopener noreferrer">VPN reviews&lt;/a> before deciding on one), which helps boost device security by protecting against IoT attacks such as botnets and MITM, is vital to enterprise-grade IoT applications.&lt;/p>
&lt;p>IoT applications generate enormous volumes of data like RFID data, streaming data, sensory data, and many others. Moreover, IoT solutions are distributed across geographical regions. Thus, the dynamic nature of IoT data demands the use of a suitable database that can allow you to efficiently manage the data  IoT solutions operate across a diverse environment; thus, it’s tough to choose an adequate database. Here are a few points to bear in mind when choosing a fitting database for your IoT system:&lt;/p>
&lt;h3 id="scalability">Scalability&lt;/h3>
&lt;p>An IoT solution scales out automatically to serve a growing load to prevent blackouts due to a lack of resources. Therefore, the database you choose for IoT applications must be scalable. Ideally, IoT databases should be linearly scalable, such that a server to a node cluster increases the throughput.  Distributed databases work best for IoT solutions as they can run on commodity hardware and scale by adding and removing servers from the database cluster as needed. On the other hand, if the application collects a small amount of data, a centralized database works.&lt;/p>
&lt;h3 id="ability-to-manage-voluminous-data">Ability to Manage Voluminous Data&lt;/h3>
&lt;p>As mentioned earlier, IoT generates vast amounts of data in real-time. The success of an open source database lies in the efficient management of data while processing events as they stream and dealing with data security. &lt;/p>
&lt;h3 id="fault-tolerant--high-availability">Fault-Tolerant &amp; High Availability&lt;/h3>
&lt;p>An ideal IoT database should be fault-tolerant and highly available. For instance, hardware and software updates are often known to interrupt normal data operations. This should not be the case. Similarly, if a node in the database cluster is down for some reason, it should still be able to read and write requests.  Open source distributed SQL database management systems like CrateDB provide automated replication of data across the cluster to ensure high availability. It can also self-heal the infected nodes.&lt;/p>
&lt;h3 id="improved-flexibility">Improved Flexibility&lt;/h3>
&lt;p>An increasing number of IoT solutions are adopting a &lt;a href="https://www.digiteum.com/cloud-fog-edge-computing-iot" target="_blank" rel="noopener noreferrer">combination of cloud and fog computing at the edge&lt;/a>. Therefore, the open source database you choose should be flexible enough to process data at the edge servers and then synchronize it between these servers and the cloud.&lt;/p>
&lt;h3 id="advanced-capabilities">Advanced Capabilities&lt;/h3>
&lt;p>Depending on the IoT solution, you would require a database that is capable of real-time data streaming, data filtering, data aggregation, real-time analytics, near-zero latency read operations, geo distribution, and schema flexibility among others. Use these questions to determine your data needs for the IoT solution and select a database that’s most suitable:&lt;/p>
&lt;ul>
&lt;li>What kind of data processing and decision making is being delegated to the edge servers?&lt;/li>
&lt;li>Is the cloud solution deployed in one geographical region, or distributed across various regions?&lt;/li>
&lt;li>What’s the volume of data transferred from the IoT device to the edge server to the central server? (peak volume)&lt;/li>
&lt;li>Does your IoT solution control any devices or actuators? Do they need a real-time response?&lt;/li>
&lt;/ul>
&lt;h2 id="top-open-source-dbs-for-iot-apps">&lt;strong>Top Open Source DBs for IoT Apps&lt;/strong>&lt;/h2>
&lt;p>It’s clear that open-source databases serve as catalysts for IoT applications, but every business has a unique requirement which means that choosing the right database for the various stages of IoT implementation is important.  Further, IoT applications are mostly heterogeneous and domain-centric. That makes it tough to choose an appropriate database. When looking for an open source database for IoT applications, it’s critical to consider parameters like scalability, availability, the ability to handle huge volumes of data, processing speed and schema flexibility, integration with varied analytical tools, security, and cost.  So, let’s end this piece with three of the best open source databases for enterprise-level IoT applications:&lt;/p>
&lt;h3 id="mongodb">MongoDB&lt;/h3>
&lt;p>A flexible and powerful open-source database that supports features like indexes, range queries, sorting, aggregations, and JSON. It also supports a rich query language for CRUD (create, read, update, delete) operations as well as data aggregation, text search, and geospatial queries. In fact, Bosch has built its IoT suite on &lt;a href="https://www.percona.com/software/mongodb" target="_blank" rel="noopener noreferrer">MongoDB&lt;/a>.  MongoDB has a few clear benefits for IoT data:&lt;/p>
&lt;ul>
&lt;li>It’s a powerful database that’s easily scalable and can effectively manage huge volumes of data.&lt;/li>
&lt;li>It is document-oriented.&lt;/li>
&lt;li>It can be used for general purposes.&lt;/li>
&lt;li>Being a NoSQL database, MongoDB uses JSON-like documents with schemas.&lt;/li>
&lt;/ul>
&lt;h3 id="cassandra">Cassandra&lt;/h3>
&lt;p>A highly scalable and distributed open-source database for managing enormous amounts of structured data across numerous commodity servers. The Apache Cassandra provides linear scale performance, simplicity, and easy distribution of data across multiple database servers, ideal for many large-scale IoT applications.  The advantages of &lt;a href="http://cassandra.apache.org/" target="_blank" rel="noopener noreferrer">Apache Cassandra&lt;/a> include:&lt;/p>
&lt;ul>
&lt;li>It’s a free and open source distributed NoSQL database management system that can handle voluminous data through multiple commodity servers. Thus, it can ensure high availability with zero single-point failure.&lt;/li>
&lt;li>It’s decentralized. Each node in the cluster is identical.&lt;/li>
&lt;li>It demonstrates high performance.&lt;/li>
&lt;li>It utilizes the immense scale of time-series data coming from devices, users, sensors, and similar mechanisms across locations.&lt;/li>
&lt;li>Each update gives you a choice of synchronous and asynchronous replication, thus giving you complete control.&lt;/li>
&lt;li>Avoids downtime as both read and write execute in real-time.&lt;/li>
&lt;/ul>
&lt;h3 id="rethinkdb">RethinkDB&lt;/h3>
&lt;p>Since RethinkDB is a super scalable JSON database for real-time web, it’s one of the best and most preferred open source databases available today. Its real-time push architecture dramatically minimizes the time and effort required to build scalable IoT apps. Plus, it has an adaptable query language for examining APIs, which is easy to set up and learn.  Here are a few reasons, &lt;a href="https://rethinkdb.com/" target="_blank" rel="noopener noreferrer">RethinkDB&lt;/a> is ideal for IoT solutions:&lt;/p>
&lt;ul>
&lt;li>It’s an adaptable query language for examining APIs.&lt;/li>
&lt;li>Offers asynchronous queries via Eventmachine in Ruby and Tornado.&lt;/li>
&lt;li>Offers a variety of mathematical operators like the floor, ceil, and round.&lt;/li>
&lt;li>If the primary server fails, the commands are automatically shifted to a new one.&lt;/li>
&lt;/ul>
&lt;p>  Handling IoT data effectively requires you to choose a suitable open source database. However, finding an efficient database can be a tricky undertaking, considering the fact that the IoT environment keeps changing. The information shared in this post will take you a step closer to understanding why open source databases help developers and organizations manage IoT data effectively.&lt;/p></content:encoded><author>Gaurav Belani</author><category>MongoDB</category><category>MySQL</category><category>Open Source Databases</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/08/iot1-scaled_hu_79a12bc897293f49.jpg"/><media:content url="https://percona.community/blog/2020/08/iot1-scaled_hu_62147ffb1751e118.jpg" medium="image"/></item><item><title>IIoT platform databases - How Mail.ru Cloud Solutions deals with petabytes of data coming from a multitude of devices</title><link>https://percona.community/blog/2020/07/24/iiot-platform-databases-how-mail-ru-cloud-solutions-deals-with-petabytes-of-data-coming-from-a-multitude-of-devices/</link><guid>https://percona.community/blog/2020/07/24/iiot-platform-databases-how-mail-ru-cloud-solutions-deals-with-petabytes-of-data-coming-from-a-multitude-of-devices/</guid><pubDate>Fri, 24 Jul 2020 14:11:14 UTC</pubDate><description> Hello, my name is Andrey Sergeyev and I work as a Head of IoT Solution Development at Mail.ru Cloud Solutions. We all know there is no such thing as a universal database. Especially when the task is to build an IoT platform that would be capable of processing millions of events from various sensors in near real-time.</description><content:encoded>&lt;p>
&lt;figure>&lt;img width="1299" height="731" sizes="(max-width: 1299px) 100vw, 1299px" srcset="https://percona.community/blog/2020/07/image6_hu_542c2b9f4ecf573e.webp 768w, https://percona.community/blog/2020/07/image6_hu_cb918f18ee1cef57.webp 480w, https://percona.community/blog/2020/07/image6_hu_3274f5aa5607d7e5.webp 1299w" src="https://percona.community/blog/2020/07/image6_hu_3274f5aa5607d7e5.webp" alt="IIoT platform databases - How Mail.ru Cloud Solutions" loading="lazy" />&lt;/figure> Hello, my name is Andrey Sergeyev and I work as a Head of IoT Solution Development at &lt;a href="https://mcs.mail.ru/" target="_blank" rel="noopener noreferrer">Mail.ru Cloud Solutions&lt;/a>. We all know there is no such thing as a universal database. Especially when the task is to build an IoT platform that would be capable of processing millions of events from various sensors in near real-time.&lt;/p>
&lt;p>Our product &lt;a href="https://mcs.mail.ru/iot/" target="_blank" rel="noopener noreferrer">Mail.ru IoT Platform&lt;/a> started as a Tarantool-based prototype. I’m going to tell you about our journey, the problems we faced and the solutions we found. I will also show you a current architecture for the modern Industrial Internet of Things platform. In this article we will look into:&lt;/p>
&lt;ul>
&lt;li>our requirements for the database, universal solutions, and the CAP theorem&lt;/li>
&lt;li>whether the database + application server in one approach is a silver bullet&lt;/li>
&lt;li>the evolution of the platform and the databases used in it&lt;/li>
&lt;li>the number of Tarantools we use and how we came to this&lt;/li>
&lt;/ul>
&lt;h2 id="mailru-iot-platform-today">Mail.ru IoT Platform today&lt;/h2>
&lt;p>Our product Mail.ru IoT Platform is a scalable and hardware-independent platform for building Industrial Internet of Things solutions. It enables us to collect data from hundreds of thousands devices and process this stream in near real-time by using user-defined rules (scripts in Python and Lua) among other tools.&lt;/p>
&lt;p>The platform can store an unlimited amount of raw data from the sources. It also has a set of ready-made components for data visualization and analysis as well as built-in tools for predictive analysis and platform-based app development.
&lt;figure>&lt;img width="1299" height="731" sizes="(max-width: 1299px) 100vw, 1299px" srcset="https://percona.community/blog/2020/07/image1_hu_2af359546fbfcb4a.webp 768w, https://percona.community/blog/2020/07/image1_hu_40fb2e3a3b618d79.webp 480w, https://percona.community/blog/2020/07/image1_hu_e3ccafb4837c032.webp 1299w" src="https://percona.community/blog/2020/07/image1_hu_e3ccafb4837c032.webp" alt="Mail.ru IoT Platform set-up" loading="lazy" />&lt;/figure> Mail.ru IoT Platform set-up[/caption] The platform is currently available for on-premise installation on customers’ facilities. In 2020 we are planning its release as a public cloud service.&lt;/p>
&lt;h2 id="tarantool-based-prototype-how-we-started">Tarantool-based prototype: how we started&lt;/h2>
&lt;p>Our platform started as a pilot project – a prototype with a single instance Tarantool. Its primary functions were receiving a data stream from the OPC server, processing the events with Lua scripts in real-time, monitoring key indicators on its basis, and generating events and alerts for upstream systems.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="488" height="539" sizes="(max-width: 488px) 100vw, 488px" srcset="https://percona.community/blog/2020/07/image3_hu_8c14cf6acd9b9b8a.webp 480w, https://percona.community/blog/2020/07/image3_hu_93272a941ea7915c.webp 488w" src="https://percona.community/blog/2020/07/image3_hu_93272a941ea7915c.webp" alt="Flowchart of the Tarantool-based prototype" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Flowchart of the Tarantool-based prototype[/caption]   This prototype has even shown itself in the field conditions of a multi-well pad in Iraq. It worked at an oil platform in the Persian Gulf, monitoring key indicators and sending data to the visualization system and the event log. The pilot was deemed successful, but then, as it often happens with prototypes, it was put into cold storage until we got our hands on it.&lt;/p>
&lt;h2 id="our-aims-in-developing-the-iot-platform">Our aims in developing the IoT platform&lt;/h2>
&lt;p>Along with the prototype, we got ourselves a challenge of creating a fully functional, scalable, and failsafe IoT platform that could then be released as a public cloud service.&lt;/p>
&lt;p>We had to build a platform with the following specifications:&lt;/p>
&lt;ol>
&lt;li>Simultaneous connection of hundreds of thousands of devices&lt;/li>
&lt;li>Receiving millions of events every second&lt;/li>
&lt;li>Datastream processing in near real-time&lt;/li>
&lt;li>Storing several years of raw data&lt;/li>
&lt;li>Analytics tools for both streaming and historic data&lt;/li>
&lt;li>Support for deployment in multiple data centers to maximize disaster tolerance&lt;/li>
&lt;/ol>
&lt;h2 id="pros-and-cons-of-the-platform-prototype">Pros and cons of the platform prototype&lt;/h2>
&lt;p>At the start of active development the prototype had the following structure:&lt;/p>
&lt;ul>
&lt;li>Tarantool that was used as a database + Application Server&lt;/li>
&lt;li>all the data was stored in Tarantool’s memory&lt;/li>
&lt;li>this Tarantool had a Lua app that performed the data reception and processing and called the user scripts with incoming data&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>This type of app structure has its advantages:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>The code and the data are stored in one place – that enables to manipulate the data right in the application memory and get rid of extra network manipulations, which are typical for traditional apps&lt;/li>
&lt;li>Tarantool uses the JIT (Just in Time Compiler) for Lua. It compiles Lua code into machine code, allowing simple Lua scripts to execute at the C-like speed (40,000 RPS per core and even higher!)&lt;/li>
&lt;li>Tarantool is based upon cooperative multitasking. This means that every call of stored procedure runs in its own coroutine-like fiber. It gives a further performance boost for the tasks with I/O operations, e.g. network manipulations&lt;/li>
&lt;li>Efficient use of resources: tools capable of handling 40,000 RPS per core are quite rare&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>There are also significant disadvantages:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>We need storing several years of raw data from the devices, but we don’t have hundreds of petabytes for Tarantool&lt;/li>
&lt;li>This item directly results from advantage #1. All of the platform code consists of procedures stored in the database, which means that any codebase update is basically a database update, and that sucks&lt;/li>
&lt;li>Dynamic scaling gets difficult because the whole system’s performance depends on the memory it uses. Long story short, you can’t just add another Tarantool to increase the bandwidth capacity without losing 24 to 32 Gb of memory (while starting, Tarantool allocates all the memory for data) and resharding the existent data. Besides, when sharding, we lose the advantage #1 – the data and the code may not be stored in the same Tarantool&lt;/li>
&lt;li>Performance deteriorates as the code gets more complex with the platform progress. This happens not only because Tarantool executes all the Lua code in a single system stream, but also because the LuaJIT goes into interpreting mode instead of compiling when dealing with complex code&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Conclusion:&lt;/strong> Tarantool is a good choice for creating an MVP, but it doesn’t work for a fully functional, easily maintained, and failsafe IoT platform capable of receiving, processing, and storing data from hundreds of thousands of devices.&lt;/p>
&lt;h2 id="two-primary-problems-that-we-wanted-to-solve">Two primary problems that we wanted to solve&lt;/h2>
&lt;p>First of all, there were two main issues we wanted to sort out:&lt;/p>
&lt;ol>
&lt;li>Ditching the concept of database + application server. We wanted to update the app code independently of the database.&lt;/li>
&lt;li>Simplifying the dynamic scaling under stress. We wanted to have an easy independent horizontal scaling of the greatest possible number of functions&lt;/li>
&lt;/ol>
&lt;p>To solve these problems, we took an innovative approach that was not well tested – the microservice architecture divided into Stateless (the applications) and Stateful (the database).&lt;/p>
&lt;p>In order to make maintenance and scaling the Stateless services out even simpler, we containerized them and adopted Kubernetes.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="515" height="397" sizes="(max-width: 515px) 100vw, 515px" srcset="https://percona.community/blog/2020/07/image9_hu_96a66ff3fd0bff99.webp 480w, https://percona.community/blog/2020/07/image9_hu_cda51dd67c1ea7c5.webp 515w" src="https://percona.community/blog/2020/07/image9_hu_cda51dd67c1ea7c5.webp" alt="Kubernetes" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now that we figured out the Stateless services, we have to decide what to do with the data.&lt;/p>
&lt;h2 id="basic-requirements-for-the-iot-platform-database">Basic requirements for the IoT platform database&lt;/h2>
&lt;p>At first, we tried not to overcomplicate things – we wanted to store all the platform data in one single universal database. Having analyzed our goals, we came up with the following list of requirements for the universal database:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>ACID transactions&lt;/strong> – the clients will keep a register of their devices on the platform, so we wouldn’t want to lose some of them upon data modification&lt;/li>
&lt;li>&lt;strong>Strict consistency&lt;/strong> – we have to get the same responses from all of the database nodes&lt;/li>
&lt;li>&lt;strong>Horizontal scaling for writing and reading&lt;/strong> – the devices send a huge stream of data that has to be processed and saved in near real-time&lt;/li>
&lt;li>&lt;strong>Fault tolerance&lt;/strong> – the platform has to be capable of manipulating the data from multiple data centers to maximize fault tolerance&lt;/li>
&lt;li>&lt;strong>Accessibility&lt;/strong> – no one would use a cloud platform that shuts down whenever one of the nodes fails&lt;/li>
&lt;li>&lt;strong>Storage volume and good compression&lt;/strong> – we have to store several years (petabytes!) of raw data that also needs to be compressed.&lt;/li>
&lt;li>&lt;strong>Performance&lt;/strong> – quick access to raw data and tools for stream analytics, including access from the user scripts (tens of thousands of reading requests per second!)&lt;/li>
&lt;li>&lt;strong>SQL&lt;/strong> – we want to let our clients run analytics queries in a familiar language&lt;/li>
&lt;/ol>
&lt;h2 id="checking-our-requirements-with-the-cap-theorem">Checking our requirements with the CAP theorem&lt;/h2>
&lt;p>Before we started examining all the available databases to see if they meet our requirements, we decided to check whether our requirements are adequate by using a well-known tool – the CAP theorem.&lt;/p>
&lt;p>The CAP theorem states that a distributed system cannot simultaneously have more than two of the following qualities:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Consistency&lt;/strong> – data in all of the nodes have no contradictions at any point in time&lt;/li>
&lt;li>&lt;strong>Availability&lt;/strong> – any request to a distributed system results in a correct response, however, without a guarantee that the responses of all system nodes match&lt;/li>
&lt;li>&lt;strong>Partition tolerance&lt;/strong> – even when the nodes are not connected, they continue working independently&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1299" height="731" sizes="(max-width: 1299px) 100vw, 1299px" srcset="https://percona.community/blog/2020/07/image11_hu_181c92a0323309c6.webp 768w, https://percona.community/blog/2020/07/image11_hu_ba0877ace6c2767f.webp 480w, https://percona.community/blog/2020/07/image11_hu_7505d41a5b2ea7b1.webp 1299w" src="https://percona.community/blog/2020/07/image11_hu_7505d41a5b2ea7b1.webp" alt="Checking our requirements with the CAP theorem" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>For instance, the Master-Slave PostgreSQL cluster with synchronous replication is a classic example of a CA system and Cassandra is a classic AP system.&lt;/p>
&lt;p>Let’s get back to our requirements and classify them with the CAP theorem:&lt;/p>
&lt;ol>
&lt;li>ACID transactions and strict (or at least not eventual) consistency are C.&lt;/li>
&lt;li>Horizontal scaling for writing and reading + accessibility is A (multi-master).&lt;/li>
&lt;li>Fault tolerance is P: if one data center shuts down, the system should stand.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1299" height="731" sizes="(max-width: 1299px) 100vw, 1299px" srcset="https://percona.community/blog/2020/07/image10_hu_88477ef5ae1ce16d.webp 768w, https://percona.community/blog/2020/07/image10_hu_d91cdcfad7d499ac.webp 480w, https://percona.community/blog/2020/07/image10_hu_66395937b0f387c3.webp 1299w" src="https://percona.community/blog/2020/07/image10_hu_66395937b0f387c3.webp" alt="ACID" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Conclusion:&lt;/strong> the universal database we require has to offer all of the CAP theorem qualities, which means that none of the existing databases can fulfill all of our needs.&lt;/p>
&lt;h2 id="choosing-the-database-based-on-the-data-the-iot-platform-works-with">Choosing the database based on the data the IoT platform works with&lt;/h2>
&lt;p>Being unable to pick a universal database, we decided to split the data into two types and choose a database for each type the database will work with.&lt;/p>
&lt;p>With a first approximation we subdivided the data into two types:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Metadata&lt;/strong> – the world model, the devices, the rules, the settings. Practically all the data except the data from the end devices&lt;/li>
&lt;li>&lt;strong>Raw data from the devices&lt;/strong> – sensor readings, telemetry, and technical information from the devices. These are time series of messages containing a value and a timestamp&lt;/li>
&lt;/ol>
&lt;h3 id="choosing-the-database-for-the-metadata">Choosing the database for the metadata&lt;/h3>
&lt;p>&lt;em>Our requirements&lt;/em>&lt;/p>
&lt;p>Metadata is inherently relational. It is typical for this data to have a small amount and be rarely modified, but the metadata is quite important. We can’t lose it, so consistency is important – at least in terms of asynchronous replication, as well as ACID transactions and horizontal read scaling.&lt;/p>
&lt;p>This data is comparatively little in amount and it will be changed rather infrequently, so you can ditch horizontal read scaling, as well as the possible inaccessibility of the read database in case of failure. That is why, in the language of the CAP theorem, we need a CA system.&lt;/p>
&lt;p>&lt;strong>What usually works.&lt;/strong> If we put a question like this, we would do with any classic relational database with asynchronous replication cluster support, e.g. PostgreSQL or MySQL.&lt;/p>
&lt;p>&lt;strong>Our platform aspects.&lt;/strong> We also needed support for trees with specific requirements. The prototype had a feature taken from the systems of the RTDB class (real-time databases) – modeling the world using a tag tree. They enable us to combine all the client devices in one tree structure, which makes managing and displaying a large number of devices much easier.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="806" height="730" sizes="(max-width: 806px) 100vw, 806px" srcset="https://percona.community/blog/2020/07/image4_hu_8aba72622bb645e3.webp 768w, https://percona.community/blog/2020/07/image4_hu_486a37bca8bf1dbc.webp 480w, https://percona.community/blog/2020/07/image4_hu_1881ac3f1e949c64.webp 806w" src="https://percona.community/blog/2020/07/image4_hu_1881ac3f1e949c64.webp" alt="This is how the device tree looks like" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is how the device tree looks like  &lt;/p>
&lt;p>This tree enables linking the end devices with the environment. For example, we can put devices physically located in the same room in one subtree, which facilitates the work with them in the future. This function is very convenient, besides, we wanted to work with RTDBs in the future, and this functionality is basically the industry standard there.&lt;/p>
&lt;p>To have a full implementation of the tag trees, a potential database must meet the following requirements:&lt;/p>
&lt;ol>
&lt;li>Support for trees with arbitrary width and depth.&lt;/li>
&lt;li>Modification of tree elements in ACID transactions.&lt;/li>
&lt;li>High performance when traversing a tree.&lt;/li>
&lt;/ol>
&lt;p>Classic relational databases can handle small trees quite well, but they don’t do as well with arbitrary trees.&lt;/p>
&lt;p>&lt;strong>Possible solution.&lt;/strong> Using two databases: a graph one for the tree and the relational one for all the other metadata.&lt;/p>
&lt;p>This approach has major disadvantages:&lt;/p>
&lt;ol>
&lt;li>To ensure consistency between two databases, you need to add an external transaction coordinator.&lt;/li>
&lt;li>This design is difficult to maintain and not so reliable.&lt;/li>
&lt;li>As a result, we get two databases instead of one, while the graph database is only required for supporting limited functionality.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="600" height="494" sizes="(max-width: 600px) 100vw, 600px" srcset="https://percona.community/blog/2020/07/image7_hu_3b08cf34ea38beb.webp 480w, https://percona.community/blog/2020/07/image7_hu_d77c5f3d46a6b1f3.webp 600w" src="https://percona.community/blog/2020/07/image7_hu_d77c5f3d46a6b1f3.webp" alt="A possible, but not a perfect solution with two databases" loading="lazy" />&lt;/figure>
A possible, but not a perfect solution with two databases  &lt;/p>
&lt;p>&lt;strong>Our solution for storing metadata.&lt;/strong> We thought a little longer and remembered that this functionality was initially implemented in a Tarantool-based prototype and it turned out very well.&lt;/p>
&lt;p>Before we continue, I would like to give an unorthodox definition of Tarantool: &lt;em>Tarantool is not a database, but a set of primitives for building a database for your specific case.&lt;/em>&lt;/p>
&lt;p>Available primitives out of the box:&lt;/p>
&lt;ul>
&lt;li>Spaces – an equivalent of tables for storing data in the databases.&lt;/li>
&lt;li>Full-fledged ACID transactions.&lt;/li>
&lt;li>Asynchronous replication using WAL logs.&lt;/li>
&lt;li>A sharding tool that supports automatic resharding.&lt;/li>
&lt;li>Ultrafast LuaJIT for stored procedures.&lt;/li>
&lt;li>Large standard library.&lt;/li>
&lt;li>LuaRocks package manager with even more packages.&lt;/li>
&lt;/ul>
&lt;p>Our CA solution was a relational + graph Tarantool-based database. We assembled perfect metadata storage with Tarantool primitives:&lt;/p>
&lt;ul>
&lt;li>Spaces for storage.&lt;/li>
&lt;li>ACID transactions – already in place.&lt;/li>
&lt;li>Asynchronous replication – already in place.&lt;/li>
&lt;li>Relations – we built them upon stored procedures.&lt;/li>
&lt;li>Trees – built upon stored procedures too.&lt;/li>
&lt;/ul>
&lt;p>Our cluster installation is classic for systems like these – one Master for writing and several Slaves with asynchronous replications for reading scaling.&lt;/p>
&lt;p>As a result, we have a fast scalable hybrid of relational and graph databases.&lt;/p>
&lt;p>One Tarantool instance is able to process thousands of reading requests, including those with active tree traversals.&lt;/p>
&lt;h3 id="choosing-the-database-for-storing-the-data-from-the-devices">Choosing the database for storing the data from the devices&lt;/h3>
&lt;p>&lt;em>Our requirements&lt;/em>&lt;/p>
&lt;p>This type of data is characterized by frequent writing and a large amount of data: millions of devices, several years of storage, petabytes of both incoming messages, and stored data. Its high availability is very important since the sensor readings are important for the user-defined rules and our internal services.&lt;/p>
&lt;p>It is important that the database offers horizontal scaling for reading and writing, availability, and fault tolerance, as well as ready-made analytical tools for working with this data array, preferably SQL-based. We can sacrifice consistency and ACID transactions, so in terms of the CAP theorem, we need an AP system.&lt;/p>
&lt;p>&lt;strong>Additional requirements.&lt;/strong> We had a few additional requirements for the solution that would store the gigantic amounts of data:&lt;/p>
&lt;ol>
&lt;li>Time Series – sensor data that we wanted to store in a specialized base.&lt;/li>
&lt;li>Open-source – the advantages of open source code are self-explanatory.&lt;/li>
&lt;li>Free cluster – a common problem among modern databases.&lt;/li>
&lt;li>Good compression – given the amount of data and its homogeneity, we wanted to compress the stored data efficiently.&lt;/li>
&lt;li>Successful maintenance – in order to minimize risks, we wanted to start with a database that someone was already actively exploiting at loads similar to ours.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Our solution.&lt;/strong> The only database suiting our requirements was ClickHouse – a columnar time-series database with replication, multi-master, sharding, SQL support, and a free cluster. Moreover, Mail.ru has many years of successful experience in operating one of the largest ClickHouse clusters.&lt;/p>
&lt;p>But ClickHouse, however good it may be, didn’t work for us.&lt;/p>
&lt;h3 id="problems-with-the-database-for-device-data-and-their-solution">Problems with the database for device data and their solution&lt;/h3>
&lt;p>&lt;strong>Problem with writing performance.&lt;/strong> We immediately had a problem with the large data stream writing performance. It needs to be delivered to the analytical database as soon as possible so that the rules analyzing the flow of events in real-time can look at the history of a particular device and decide whether to raise an alert or not.&lt;/p>
&lt;p>&lt;strong>Solution.&lt;/strong> ClickHouse is not good with multiple single inserts, but works well with large packets of data, easily coping with writing millions of lines in batches. We decided to buffer the incoming data stream, and then paste this data in batches.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1298" height="466" sizes="(max-width: 1298px) 100vw, 1298px" srcset="https://percona.community/blog/2020/07/image5_hu_6669c40c4b7c0022.webp 768w, https://percona.community/blog/2020/07/image5_hu_e678b128c31fac33.webp 480w, https://percona.community/blog/2020/07/image5_hu_6e2037100b89d7b1.webp 1298w" src="https://percona.community/blog/2020/07/image5_hu_6e2037100b89d7b1.webp" alt="This is how we dealt with poor writing performance" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is how we dealt with poor writing performance  &lt;/p>
&lt;p>The writing problems were solved, but it cost us several seconds of lag between the data coming into the system and its appearance in our database.&lt;/p>
&lt;p>This is critical for various algorithms that react to the sensor readings in real-time.&lt;/p>
&lt;p>&lt;strong>Problem with reading performance.&lt;/strong> Stream analytics for real-time data processing constantly needs information from the database – tens of thousands of small queries. On average, one ClickHouse node handles about a hundred analytical queries at any time. It was created to infrequently process heavy analytical queries with large amounts of data. Of course, this is not suitable for calculating trends in the data stream from hundreds of thousands of sensors.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="607" height="363" sizes="(max-width: 607px) 100vw, 607px" srcset="https://percona.community/blog/2020/07/image2_hu_8e360df096798cb1.webp 480w, https://percona.community/blog/2020/07/image2_hu_f1c11204eca46705.webp 607w" src="https://percona.community/blog/2020/07/image2_hu_f1c11204eca46705.webp" alt="ClickHouse doesn’t handle a large number of queries well" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>ClickHouse doesn’t handle a large number of queries well&lt;/p>
&lt;p>&lt;strong>Solution.&lt;/strong> We decided to place a cache in front of Clickhouse. The cache was meant to store the hot data that has been requested in the last 24 hours most often.&lt;/p>
&lt;p>24 hours of data is not a year but still quite a lot – so we need an AP system with horizontal scaling for reading and writing and a focus on performance while writing single events and numerous readings.&lt;/p>
&lt;p>We also need high availability, analytic tools for time series, persistence, and built-in TTL. So, we needed a fast ClickHouse that could store everything in memory. Being unable to find any suitable solutions, we decided to build one based on the Tarantool primitives:&lt;/p>
&lt;ol>
&lt;li>Persistence – check (WAL-logs + snapshots).&lt;/li>
&lt;li>Performance – check; all the data is in the memory.&lt;/li>
&lt;li>Scaling – check; replication + sharding.&lt;/li>
&lt;li>High availability – check.&lt;/li>
&lt;li>Analytics tools for time series (grouping, aggregation, etc.) – we built them upon stored procedures.&lt;/li>
&lt;li>TTL – built upon stored procedures with one background fiber (coroutine).&lt;/li>
&lt;/ol>
&lt;p>The solution turned out to be powerful and easy to use. One instance handled 10,000 reading RPCs, including analytic ones.&lt;/p>
&lt;p>Here is the architecture we came up with:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1030" height="652" sizes="(max-width: 1030px) 100vw, 1030px" srcset="https://percona.community/blog/2020/07/image8_hu_e9d5b9e8c4b5375.webp 768w, https://percona.community/blog/2020/07/image8_hu_1c77b31a884ce4d4.webp 480w, https://percona.community/blog/2020/07/image8_hu_4944ed003b182b34.webp 1030w" src="https://percona.community/blog/2020/07/image8_hu_4944ed003b182b34.webp" alt="Final architecture: ClickHouse as the analytic database and the Tarantool cache storing 24 hours of data. " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Final architecture: ClickHouse as the analytic database and the Tarantool cache storing 24 hours of data.  &lt;/p>
&lt;h2 id="a-new-type-of-data--the-state-and-its-storing">A new type of data – the state and it’s storing&lt;/h2>
&lt;p>We found a specific database for each type of data, but as the platform developed, another one appeared – the status. The status consists of current statuses of sensors and devices, as well as some global variables for stream analytics rules.&lt;/p>
&lt;p>Let’s say we have a lightbulb. The light may be either on or off, and we always need to have access to its current state, including one in the rules. Another example is a variable in stream rules – e.g., a counter of some sort.&lt;/p>
&lt;p>This type of data needs frequent writing and fast access but doesn’t take a lot of space.&lt;/p>
&lt;p>Metadata storage doesn’t suit this type of data well, because the status may change quite often and we only have one Master for writing. Durable and operating storage doesn’t work well too, because our status was last changed three years ago, and we need to have quick reading access.&lt;/p>
&lt;p>This means that the status database needs to have horizontal scaling for reading and writing, high availability, fault tolerance, and consistency on the values/documents level. We can sacrifice global consistency and ACID transactions.&lt;/p>
&lt;p>Any Key-Value or a document database should work: Redis sharding cluster, MongoDB, or, once again, Tarantool.&lt;/p>
&lt;p>Tarantool advantages:&lt;/p>
&lt;ol>
&lt;li>It is the most popular way of using Tarantool.&lt;/li>
&lt;li>Horizontal scaling – check; asynchronous replication + sharding.&lt;/li>
&lt;li>Consistency on the document level – check.&lt;/li>
&lt;/ol>
&lt;p>As a result, we have three Tarantools that are used differently: one for storing metadata, a cache for quick reading from the devices, and one for storing status data.&lt;/p>
&lt;h2 id="how-to-choose-a-database-for-your-iot-platform">How to choose a database for your IoT platform&lt;/h2>
&lt;ol>
&lt;li>There is no such thing as a universal database.&lt;/li>
&lt;li>Each type of data should have its own database, the one most suitable.&lt;/li>
&lt;li>There is a chance you may not find a fitting database in the market.&lt;/li>
&lt;li>Tarantool can work as a basis for a specialized database&lt;/li>
&lt;/ol></content:encoded><author>Andrey Sergeev</author><category>Advanced Level</category><category>Cache</category><category>ClickHouse</category><category>DevOps</category><category>IoT</category><category>Kubernetes</category><category>Kubernetes</category><category>Lua</category><category>NoSQL</category><category>Open Source Databases</category><category>SQL</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/07/image6_hu_919c9cc21495b346.jpg"/><media:content url="https://percona.community/blog/2020/07/image6_hu_cedf6902f00c5464.jpg" medium="image"/></item><item><title>MariaDB Server Fest: Call for Papers</title><link>https://percona.community/blog/2020/06/26/mariadb-server-fest-call-for-papers/</link><guid>https://percona.community/blog/2020/06/26/mariadb-server-fest-call-for-papers/</guid><pubDate>Fri, 26 Jun 2020 21:42:28 UTC</pubDate><description>In the week of 14-20 September 2020, MariaDB Foundation will host the MariaDB Server Fest Online Conference. We welcome the Percona Community not just to participate, but also to submit papers for the event. We already have Peter Zaitsev joining as keynoter; we hope for more to come.</description><content:encoded>&lt;p>In the week of 14-20 September 2020, MariaDB Foundation will host the MariaDB Server Fest Online Conference. We welcome the Percona Community not just to participate, but also to submit papers for the event. We already have Peter Zaitsev joining as keynoter; we hope for more to come.&lt;/p>
&lt;p>Our target audience are the users of MariaDB Server – current and future ones. We are looking for use cases, practices, tools and insights from our user base as well as from application developers, service providers and other experts.&lt;/p>
&lt;p>When planning and phrasing your CfP submission at &lt;a href="https://mariadb.org/fest2020cfp/" target="_blank" rel="noopener noreferrer">https://mariadb.org/fest2020cfp/&lt;/a>, think about what makes MariaDB Server unique, and what insights you can give the demanding audience.&lt;/p>
&lt;ul>
&lt;li>Our audience is interested in your insights about new cool features of the latest releases, but also in underused MariaDB functionality that has been there for a while.&lt;/li>
&lt;li>Functionality such as system versioned tables, JSON functionality, and security features is interesting, and the same goes for usage patterns and best practices.&lt;/li>
&lt;li>Share your knowledge of PL/SQL, SEQUENCEs and other Oracle compatibility features, but also in experiences from overall migration strategies.&lt;/li>
&lt;li>Our audience is interested in comparing HA, Galera and general replication functionality to that of other similar databases, but would likely want to avoid overly confrontational flame wars on, say, Global Transaction ID.&lt;/li>
&lt;li>Developers and DBAs are used to seeing MariaDB positioned in contrast to MySQL (level of compatibility; differences in feature set), but may also find it insightful with comparisons to PostgreSQL, MongoDB and Oracle.&lt;/li>
&lt;li>Developers, sysadmins and devops are focused on technology and functionality, but is also very mindful of the implications of release schedules, security fix processes, and engaging the community in submitting code contributions.&lt;/li>
&lt;/ul>
&lt;p>For more about our conference, see our announcement at &lt;a href="https://mariadb.org/fest/" target="_blank" rel="noopener noreferrer">https://mariadb.org/fest/&lt;/a> and &lt;a href="https://mariadb.org/fest2020cfp/" target="_blank" rel="noopener noreferrer">https://mariadb.org/fest2020cfp/&lt;/a>.&lt;/p>
&lt;p>Finally: thank you to Tom Basil of Percona, who opened up the opportunity for us to write this guest blog on the Percona Community Blog!&lt;/p>
&lt;p>We hope for many interesting submissions – and, later on, attendees – from the Percona Community. Footnote: The Call for Papers is open for one more week, until the end of June.&lt;/p></content:encoded><author>Kaj Arnö</author><category>kaj.arno</category><category>Events</category><category>MariaDB</category><category>MySQL</category><category>mysql-and-variants</category><media:thumbnail url="https://percona.community/blog/2020/06/MDBS_Fest_logowhite_bg_hu_bcf01eb058e3a10.jpg"/><media:content url="https://percona.community/blog/2020/06/MDBS_Fest_logowhite_bg_hu_980005dd80e7e01.jpg" medium="image"/></item><item><title>Cassandra Where and How by John Schulz</title><link>https://percona.community/blog/2020/06/24/cassandra-where-and-how-by-john-schulz/</link><guid>https://percona.community/blog/2020/06/24/cassandra-where-and-how-by-john-schulz/</guid><pubDate>Wed, 24 Jun 2020 12:20:22 UTC</pubDate><description>If Percona Live ONLINE had graded its talks by skill level this year, John Schulz’s talk would have been essential viewing in the Beginners track. (You can watch all the event’s presentations now on Percona’s YouTube channel.) This talk was a good overview and meant for anyone who had heard of the Apache Cassandra distributed database but wasn’t sure whether it would be suitable for their project or not.</description><content:encoded>&lt;p>If Percona Live ONLINE had graded its talks by skill level this year, John Schulz’s talk would have been essential viewing in the Beginners track. (You can watch all the event’s presentations now on &lt;a href="https://www.youtube.com/user/PerconaMySQL/videos" target="_blank" rel="noopener noreferrer">Percona’s YouTube channel.&lt;/a>) This talk was a good overview and meant for anyone who had heard of the Apache Cassandra distributed database but wasn’t sure whether it would be suitable for their project or not.&lt;/p>
&lt;p>Database-veteran, John Schulz has been tinkering with Cassandra for about a decade and to help anyone get started he gave a whistle-stop tour of the Cassandra ecosystem. He introduced Apache Cassandra by laying out some important characteristics of the database. These include the way Cassandra is designed to handle high-traffic volumes, especially writes, and is designed from the ground-up for high availability. John briefly talked about the ‘democratized nature’ of the database; how all its nodes are designed to be equal. However, while Cassandra is designed to scale linearly, he stressed that this ability comes with some serious caveats: “You have to understand the way it was designed,” John cautioned an audience of over 500 attendees. “You have to understand how you need to model data with it, otherwise its linear scaling will go out the window.”&lt;/p>
&lt;h2 id="not-relational">Not relational&lt;/h2>
&lt;p>Cassandra has many strengths, but it’s not suitable for every use case. For instance, John said he would discourage using Cassandra for analytics as “it’s not a massive parallel processing engine.”&lt;/p>
&lt;p>He also highlighted the fact that Cassandra uses an SQL-like language called the &lt;a href="https://en.wikipedia.org/wiki/Apache_Cassandra#Cassandra_Query_Language" target="_blank" rel="noopener noreferrer">Cassandra Query Language (CQL)&lt;/a>, which despite its similarities is definitely not SQL. Similarly, while you can add &lt;a href="https://spark.apache.org/sql/" target="_blank" rel="noopener noreferrer">Spark SQL&lt;/a> to Cassandra and perform &lt;a href="https://en.wikipedia.org/wiki/Join_%28SQL%29" target="_blank" rel="noopener noreferrer">JOINs&lt;/a>, Cassandra is not a relational database and shouldn’t be used as one. He also warned against implementing &lt;a href="https://en.wikipedia.org/wiki/Record_locking" target="_blank" rel="noopener noreferrer">locks&lt;/a> in Cassandra. Apparently, he’s seen many customers do this only to regret it later. In fact, he suggested that if using a lock is essential for your application, then perhaps you shouldn’t be looking at Cassandra.&lt;/p>
&lt;p>After cautioning his virtual attendees, John shared some of the circumstances and use cases where Cassandra does excel. As a general principle, Cassandra works best in environments where the database writes exceed the reads by a large margin and where the sheer amount of traffic would normally overwhelm a traditional relational database.&lt;/p>
&lt;p>By way of example, John said that Cassandra works well for tracking ad hit rates. The database is also popularly used in the IoT industry for capturing raw data from devices, such as fitness trackers and vehicles. Also, many phone companies in North America are using Cassandra for customer service and a number of companies use it to provide metrics collection as a service.&lt;/p>
&lt;h2 id="first-steps">First steps&lt;/h2>
&lt;p>Before getting started with Cassandra, John strongly recommended setting aside some time to design your database: “Badly designed data models, produce badly performing databases.”&lt;/p>
&lt;p>He suggested a couple of resources that would help with that including an &lt;a href="https://cassandra.apache.org/doc/latest/data_modeling/" target="_blank" rel="noopener noreferrer">overview of the topic from the Apache Cassandra project itself&lt;/a>.&lt;/p>
&lt;p>Next, he shared some of the questions you need to ask yourself before using Cassandra. For instance, what’s your main purpose for using Cassandra? The answer to that question will have a bearing on how you want to run Cassandra. That’s because the database offers plenty of options that range from a traditional data center environment to various cloud solutions. You can run Cassandra on your laptop, which is a good environment for tinkering with it. For a production environment though you can deploy Cassandra on physical servers, or inside VMs, or wrapped in containers.&lt;/p>
&lt;p>The next piece of the puzzle is to decide on a Cassandra flavour or distribution. John rounded up some of the most popular including Apache Cassandra, DataStax Enterprise, Scylla Open Source and Enterprise, Yugabyte, CosmosDB, Amazon Keyspaces, and Elassandra. He spent some time explaining them all and the key differences between them, but besides Apache Cassandra and DataStax Enterprise, he classified all other solutions as Cassandra API upstarts that look and behave like Cassandra, but aren’t exactly Cassandra under the covers. He was particularly excited about Elassandra, the mashup of Elasticsearch and Cassandra and pointed out that the former’s global index helps negate the limitations of Cassandra’s secondary indexes that are local-only by default.&lt;/p>
&lt;h2 id="at-your-service">At your service&lt;/h2>
&lt;p>You can run Cassandra on various platforms, though John recommended using one of the Database-as-a-Service (DBaaS) providers as he felt it made very little sense to do it any other way. He briefly talked about some of the most popular services including InstaClustr, DataStax Astra, Amazon KeySpaces, Scylla Cloud, IBM Compose for Scylla, YugaByte Cloud, and CosmosDB.&lt;/p>
&lt;p>The main advantage of these services, John felt, was that they get you a Cassandra cluster instantly. Furthermore, they also come with lots of useful features such as automatic backups, automatic repairs, as well as monitoring. However, if you don’t want to deploy Cassandra on your own hardware, John supplied a list of things you’ll want to think about.&lt;/p>
&lt;p>He suggested using an automation tool, such as Chef, Puppet, Ansible, to build your clusters. He also recommended using a log aggregator and monitoring the cluster in real-time. He cautioned anyone looking to deploy Cassandra to never run an installation with a single node. John says that while you can do this, you won’t be able to observe all of the interactions that go on between the nodes, which will eventually affect the real-world performance and behaviour of your application. However, John recommended running a cluster of at least n nodes where n equals your replication factor. This is a talk in its own right, but, in essence, he suggested a replication factor of at least three.&lt;/p>
&lt;p>In the final section of his talk he covered the two mechanisms for deploying Cassandra: inside a Docker container and with the &lt;a href="https://github.com/riptano/ccm" target="_blank" rel="noopener noreferrer">Cassandra Cluster Manager (CCM)&lt;/a>. Written in Python, John says CCM makes starting a Cassandra cluster on your laptop or desktop, or even a Raspberry Pi, just as easy as using a Database-as-a-Service option on the cloud. He ended by detailing the procedure for both mechanisms using which you can spin up a Cassandra cluster in a matter of minutes. You can watch the whole of &lt;a href="https://www.percona.com/resources/videos/cassandra-where-and-how-john-schulz-percona-live-online-2020" target="_blank" rel="noopener noreferrer">John Schulz’s Apache Cassandra talk&lt;/a> through the link.&lt;/p></content:encoded><author>Mayank Sharma</author><category>Mayank Sharma</category><category>Cassandra</category><category>DBaaS</category><category>DevOps</category><category>Docker</category><category>Events</category><category>Open Source Databases</category><category>Percona Live</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/PLO-Card-Cassandra_hu_7c6defe97179165d.jpg"/><media:content url="https://percona.community/blog/2020/06/PLO-Card-Cassandra_hu_eb3f6bd7bb1747b9.jpg" medium="image"/></item><item><title>Percona Live ONLINE Talk: Optimize and Troubleshoot MySQL using Percona Monitoring and Management by Peter Zaitsev</title><link>https://percona.community/blog/2020/06/23/percona-live-online-talk-optimize-and-troubleshoot-mysql-using-percona-monitoring-and-management-by-peter-zaitsev/</link><guid>https://percona.community/blog/2020/06/23/percona-live-online-talk-optimize-and-troubleshoot-mysql-using-percona-monitoring-and-management-by-peter-zaitsev/</guid><pubDate>Tue, 23 Jun 2020 15:09:57 UTC</pubDate><description>Incorporating a database in an organization is a complicated task that involves a lot of people besides the DBAs. This is something that Peter Zaitsev, co-founder and CEO of Percona, understands very well.</description><content:encoded>&lt;p>Incorporating a database in an organization is a complicated task that involves a lot of people besides the DBAs. This is something that Peter Zaitsev, co-founder and CEO of Percona, understands very well.&lt;/p>
&lt;p>In the build-up to his hands-on presentation with the open source &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management&lt;/a> (PMM) platform, Peter spoke about how inducting a database in an organization is a constant tussle between the developers, the management and the DBAs. While the developers want a solution that just works, the managers don’t want the database to break the bank: “The DBAs just want to make sure they don’t spend too much time keeping them both happy,” he shared.&lt;/p>
&lt;p>This is why, Peter argues, DBAs want to make sure the databases in their realm are optimized for performance. Like security, performance optimization is an on-going process that begins during development and continues into the production environment as well.&lt;/p>
&lt;h2 id="cover-all-bases">Cover all bases&lt;/h2>
&lt;p>Based on his experience, Peter talked about the two factors that impact the performance of a database. On the one hand, you have applications that are responsible for the volume and type of queries they generate. If an application sends an unoptimized query it can put the database under unnecessary strain. On the other hand, you have hardware resources that when stretched to the limit can even delay the simplest of queries.&lt;/p>
&lt;p>Peter pointed out that PMM takes both these aspects into consideration, before launching into his hands-on demo of the latest version of the platform, PMM 2. He began with an overview of the new features in the release particularly its ability to look at groups of servers instead of a single server, something that Peter refers to as “treating the servers as a herd and not as pets”.&lt;/p>
&lt;p>He began the demo with the Query Analytics dashboard that shows all the database queries running across all deployed servers. He ran through the various metrics on which DBAs can sort the queries to get different kinds of results, such as the list of queries that run most frequently or the queries that take the longest to complete.&lt;/p>
&lt;p>As looking at averages doesn’t usually make a lot of sense for performance optimization, Peter demonstrated how you can use PMM 2 to drill down to particular problematic queries. He used the platform to pinpoint a particular inefficient query that was returning one row on average, but only after scanning about 100,000 rows leading to degradation in performance.&lt;/p>
&lt;h2 id="a-360-degree-view">A 360-degree view&lt;/h2>
&lt;p>He also demonstrated how DBAs can visualize the performance of the database using different parameters. For instance, you can sort it by users, which is particularly useful if you’ve followed the good practice of configuring different apps to run with different users. Viewing loads by users will help you identify the applications that are consuming the most resources.&lt;/p>
&lt;p>Next, he headed to the Node Summary dashboard, which is useful for observing the usage of the hardware resources on the servers. This dashboard tracks several additional parameters that help DBAs make more sense of the resource usage. For instance, instead of just CPU usage, you’re also able to see CPU saturation and max core utilization. The latter is particularly useful since single queries in MySQL can only execute on one CPU core. Peter showed how you can use this dashboard to make sure your multi-core CPU is being used efficiently.&lt;/p>
&lt;p>He ran through similar examples with memory utilization and Disk IO throughput, both of which display additional parameters to help you ensure the concerned resource is being used efficiently. He also demonstrated the MySQL Instance summary dashboard that displays various information about the MySQL servers as well as the InnoDB Details dashboard, which visualizes all kinds of InnoDB activity and is useful for identifying and diagnosing bottlenecks. One metric that Peter pointed out was InnoDB pending IOs, which can be very valuable for weeding out storage bottlenecks, especially when using cloud storage.&lt;/p>
&lt;h2 id="advanced-usage">Advanced usage&lt;/h2>
&lt;p>One of the interesting features of PMM 2 is that you can ask it to &lt;a href="https://www.percona.com/blog/2020/03/30/advanced-query-analysis-in-percona-monitoring-and-management-with-direct-clickhouse-access/" target="_blank" rel="noopener noreferrer">use ClickHouse&lt;/a> to store query performance data. Peter demoed how you can access ClickHouse on PMM 2 and showed off a dashboard he built on top that isn’t yet part of the platform but promised to share it publicly soon.&lt;/p>
&lt;p>PMM 2 is &lt;a href="https://www.percona.com/blog/2019/11/22/designing-grafana-dashboards/" target="_blank" rel="noopener noreferrer">powered by Grafana&lt;/a> and Peter rounded up the presentation by sharing some interesting tips and tricks for using Grafana, such as ad-hoc filtering, which you can use to filter a dashboard by any of the defined clauses. For instance, Peter showed how you can use it to look at all the queries that send a maximum of ten rows.&lt;/p>
&lt;p>One of the new additions in PMM 2 is the Security Threat tool and Peter briefly ran through this during his demonstration. The tool runs daily checks for common database security issues and flags any non-compliance.&lt;/p>
&lt;p>Fielding questions, Peter clarified that while he focussed on MySQL, PMM 2 supports MariaDB as well. PMM monitoring doesn’t add much overhead and at the end of the day will surely help you save a lot more resources than it consumes.&lt;/p>
&lt;p>You can &lt;a href="https://www.percona.com/resources/videos/optimize-and-troubleshoot-mysql-using-pmm-2-peter-zaitsev-percona-live-online-2020" target="_blank" rel="noopener noreferrer">watch Peter’s presentation&lt;/a> and follow along on the publicly accessible &lt;a href="https://pmmdemo.percona.com/" target="_blank" rel="noopener noreferrer">PMM 2 demo server&lt;/a>.&lt;/p></content:encoded><author>Mayank Sharma</author><category>Mayank Sharma</category><category>DevOps</category><category>MariaDB</category><category>Monitoring</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Percona Monitoring and Management</category><category>PMM</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_1395b6e2186771a6.jpg"/><media:content url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_c0e4c47b55fa22a9.jpg" medium="image"/></item><item><title>Making a Tarantool-Based Investment Business Core for Alfa-Bank</title><link>https://percona.community/blog/2020/06/19/making-a-tarantool-based-investment-business-core-for-alfa-bank/</link><guid>https://percona.community/blog/2020/06/19/making-a-tarantool-based-investment-business-core-for-alfa-bank/</guid><pubDate>Fri, 19 Jun 2020 13:23:49 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="780" height="390" sizes="(max-width: 780px) 100vw, 780px" srcset="https://percona.community/blog/2020/06/image3-1_hu_c09e1e0321981500.webp 768w, https://percona.community/blog/2020/06/image3-1_hu_6cc9474ac2b67951.webp 480w, https://percona.community/blog/2020/06/image3-1_hu_5a54d9a4eea4bd8b.webp 780w" src="https://percona.community/blog/2020/06/image3-1_hu_5a54d9a4eea4bd8b.webp" alt="A still from “Our Secret Universe: The Hidden Life of the Cell”" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>A still from “Our Secret Universe: The Hidden Life of the Cell”&lt;/p>
&lt;p>Investment business is one of the most complex domains in the banking world. It’s about not just credits, loans, and deposits — there are also securities, currencies, commodities, derivatives, and all kinds of complex stuff like structured products.&lt;/p>
&lt;p>Recently, people have become increasingly aware of their finances. More and more get involved in securities trading. Individual investment accounts have emerged not so long ago. They allow you to trade in securities and get tax credits or avoid taxes at the same time. All clients coming to us want to manage their portfolios and see their reporting on-line. Most frequently, these are multi-product portfolios, which means that people are clients of different business areas.&lt;/p>
&lt;p>Moreover, the demands of regulators, both Russian and international, also grow.&lt;/p>
&lt;p>To meet the current needs and lay a foundation for future upgrades, we’ve developed our Tarantool-based investment business core.&lt;/p>
&lt;p>A few statistics: Alfa Bank’s investment business provides brokerage services to individuals and entities enabling them to trade in various securities markets; custody services holding their securities; trust management services for big private capital owners, and securities emission services to other companies. Talking about Alfa Bank’s investment business, we mean over 3 thousand quotations per second which come from different trading platforms. Over 300 thousand transactions per trading day are closed on behalf of the bank or its clients. There are up to 5 thousand orders executed every second on domestic and international platforms. On top of that, all clients, both domestic and international, want to see their positions in real-time.&lt;/p>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>Starting from the early 2000s, our investment businesses are developing independently: exchange business, brokerage services, currency trading, and over-the-counter trading in securities and various derivatives. As a result, we got into the pitfall of functional wells. What is it? Each business area has its systems that duplicate each other’s functions. Each system has its own data model, although they use one and the same concepts: transactions, instruments, counterparties, quotations, and others. As each system has developed independently, a diverse “technology zoo” has emerged.&lt;/p>
&lt;p>Additionally, the systems’ codebase has become rather old, because some products were conceived back in the mid-1990s. This deterred the development process, and there were performance issues.&lt;/p>
&lt;h2 id="requirements-for-a-new-solution">Requirements for a new solution&lt;/h2>
&lt;p>Those in the business realized that a technology transformation was vital for continued growth. We were assigned the following tasks:&lt;/p>
&lt;ol>
&lt;li>Collect all business data in a single fast storage and within a single data model.&lt;/li>
&lt;li>The data should not be lost or modified.&lt;/li>
&lt;li>The data had to be versioned because the regulator could request historical data for past years at any time.&lt;/li>
&lt;li>We had not just to create some new fancy database management system but to make a platform for delivering on business objectives.&lt;/li>
&lt;/ol>
&lt;p>Apart from that, our architects named their own terms:&lt;/p>
&lt;ol>
&lt;li>The new solution should be enterprise-class, which means it should have been already proven in a major business.&lt;/li>
&lt;li>The solution’s operation should be mission-critical. This means we should be present in several data centers at the same time and safely survive the shutdown of a single data center.&lt;/li>
&lt;li>The system should be horizontally scalable. In fact, all our current systems are only vertically scalable, and we already have no room for further growth due to low rates of hardware performance enhancement. So, we are now at the point where we need to have a horizontally scalable system to survive.&lt;/li>
&lt;li>Apart from that, we were told that the solution should be cost-efficient.&lt;/li>
&lt;/ol>
&lt;p>We followed a standard approach: specified the requirements and contacted our procurement unit. From them, we received a list of companies that generally agreed to do that for us. We told them all about the assignment and received solution quotations from six of them.&lt;/p>
&lt;p>We in the banking business take no one’s word for anything and like to test everything ourselves. Thus, it was a prerequisite for the bidders to pass load tests. We specified load testing assignments, and three companies of six agreed to implement a prototype solution for their own account on the basis of in-memory technology for testing.&lt;/p>
&lt;p>I won’t tell here how we were testing everything and how much time it took, just the final result: a prototype Tarantool-based solution from Mail.ru Group’s developers’ team showed the best performance in loading tests. We signed a contract and started development. There were four developers from Mail.ru Group and three from Alfa Bank, three system analysts, a solution architect, a product owner, and a Scrum master.&lt;/p>
&lt;p>Now I’m going to tell you how our system grew and evolved, and what we did, and why.&lt;/p>
&lt;h2 id="development">Development&lt;/h2>
&lt;p>First, we asked ourselves a question about how to retrieve data from our current systems. We concluded that HTTP was quite suitable because all the current systems communicated with each other sending XML or JSON via HTTP.&lt;/p>
&lt;p>We use an HTTP server built into Tarantool because we have no need to terminate SSL sessions — its capacity is more than enough.&lt;/p>
&lt;p>As I already said, all our systems exist in different data models, and, at the input, we need to bring the object to the model that we specify for us. We needed a language enabling data conversion. We chose imperative Lua. We execute all the code for data conversion in a sandbox — it’s a safe place from which the running code cannot escape. To do that, we simply make a load string of the desired code, creating an environment with features that cannot block or disrupt anything.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="732" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/01_hu_4d2a20e89be069ba.webp 768w, https://percona.community/blog/2020/06/01_hu_a6e7f2f889b0503f.webp 480w, https://percona.community/blog/2020/06/01_hu_1554f4ed4f968f10.webp 1400w" src="https://percona.community/blog/2020/06/01_hu_1554f4ed4f968f10.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After conversion, data needs to be checked for conformity to the model we are creating. We had a long discussion of what the model should look like, and what language to use to define it. Our final choice was Apache Avro because it is a simple language supported by Tarantool. New versions of the model and custom code can be sent to operation several times a day, load or no load, round the clock, and we can adjust to changes really fast.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/02_hu_5fe40ea9a2b59b13.webp 768w, https://percona.community/blog/2020/06/02_hu_f1a681dfe7c8713a.webp 480w, https://percona.community/blog/2020/06/02_hu_fb96660ceb3b8417.webp 1400w" src="https://percona.community/blog/2020/06/02_hu_fb96660ceb3b8417.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>After checking, the data needs to be saved. We do this using vshard (we have geographically dispersed replica shards).&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/03_hu_f2a40c61ecb9634.webp 768w, https://percona.community/blog/2020/06/03_hu_7048f13bf055385f.webp 480w, https://percona.community/blog/2020/06/03_hu_1c48282c641d9d32.webp 1400w" src="https://percona.community/blog/2020/06/03_hu_1c48282c641d9d32.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;p>What is special about this is that most systems that send data to us don’t care if we receive the data or not. So we implemented a repair queue in the very beginning. What is it? If for some reason, an object has not passed data conversion or check, we confirm receipt anyway but save the object in the repair queue. It is coherent and is located in basic storage with business data. We wrote the admin interface, various metrics, and alerts for it early on. As a result, we don’t lose data. Even if something changes in the source, if data model changes, we can notice this at once and adjust accordingly.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/04_hu_5ffd427fbc38ae45.webp 768w, https://percona.community/blog/2020/06/04_hu_8e58e7d6c97607dd.webp 480w, https://percona.community/blog/2020/06/04_hu_c2d5140a26575a8c.webp 1400w" src="https://percona.community/blog/2020/06/04_hu_c2d5140a26575a8c.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;p>Now we have to learn how to retrieve the saved data. We gave our systems a thorough review and saw that on a classic stack from Java and Oracle there was always some ORM that converted relational data to object data. So why not just feed objects to systems in the form of a graph? That is why we gladly chose GraphQL which satisfied our needs. It enables data to be obtained in the form of graphs and retrieve only what is needed at the moment. Even API can be versioned with sufficient flexibility.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="738" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/05_hu_704959fb2649c7a9.webp 768w, https://percona.community/blog/2020/06/05_hu_353c180bc481b40f.webp 480w, https://percona.community/blog/2020/06/05_hu_f450e511e802ad5f.webp 1400w" src="https://percona.community/blog/2020/06/05_hu_f450e511e802ad5f.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Almost at once, we realized that retrievable data was not enough for us. We made functions which could be linked to objects in a model — essentially, calculated fields. That is, we link to a field some function which calculates mean quotation price, for example. An external user who requests the data doesn’t even know that the field is a calculated field.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/06_hu_229c916406048279.webp 768w, https://percona.community/blog/2020/06/06_hu_9d1746a41093b9ef.webp 480w, https://percona.community/blog/2020/06/06_hu_71e8a5c5e65ca61f.webp 1400w" src="https://percona.community/blog/2020/06/06_hu_71e8a5c5e65ca61f.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We implemented an authentication system.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/07_hu_c3720af5c5e195b8.webp 768w, https://percona.community/blog/2020/06/07_hu_c44d2dcd6014db7c.webp 480w, https://percona.community/blog/2020/06/07_hu_9f3f5a895046bbe5.webp 1400w" src="https://percona.community/blog/2020/06/07_hu_9f3f5a895046bbe5.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Then we noticed that several roles were crystallizing out in our solution. Role is a kind of an aggregator of functions. Roles normally have different equipment utilization profiles:&lt;/p>
&lt;ul>
&lt;li>T-Connect: processes inbound connections, limited in CPU usage, consumes less memory, and doesn’t store the status.&lt;/li>
&lt;li>IB-Core: converts data it receives via Tarantool protocol, which means that it manipulates with tables. It doesn’t store status as well, and it can be scaled.&lt;/li>
&lt;li>Storage: only saves data and uses no logic. The most simple interfaces are implemented in this role. It can be scaled through vshard.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/08_hu_60ce0bf13758e98.webp 768w, https://percona.community/blog/2020/06/08_hu_c69fc9d456c262f7.webp 480w, https://percona.community/blog/2020/06/08_hu_bcb6d4b1b1f502cb.webp 1400w" src="https://percona.community/blog/2020/06/08_hu_bcb6d4b1b1f502cb.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;p>That is, using roles, we unlinked different parts of the cluster from each other, which can be scaled independently.&lt;/p>
&lt;p>This way, we created an asynchronous write of transactional data flow and a repair queue with an admin interface. The write is asynchronous from the business perspective: once we have reliably written data in our system, no matter where exactly, we will be able to confirm that. If we don’t confirm, then something went wrong, and the data needs to be resent. This is what writing asynchrony is about.&lt;/p>
&lt;h2 id="testing">Testing&lt;/h2>
&lt;p>At the very start, we decided to instill test-driven development. We write unit tests in Lua using tarantool/tap framework, and integration tests in Python using pytest framework. Doing that, we got both developers and analysts involved in integration test writing.&lt;/p>
&lt;p>How do we use test-driven development?&lt;/p>
&lt;p>When we want a new feature, we try to write a test for it first. Once a bug is found, we always write a test before fixing it. It is hard to work this way at first, and there are a misunderstanding and even opposition on the part of the staff, like: “Let’s fix it now, then do something new, and then cover it with tests.” However, this almost never happens.&lt;/p>
&lt;p>So one needs to will oneself into writing tests in the first place, and make others do the same. Take my word for it, test-driven development pays even in the short term. It will make your life easier. In our perception, about 99% of all code is covered with tests. Quite a lot as it seems, but we have no problem with it: tests are run for every commit.&lt;/p>
&lt;p>Yet, we like load testing most. We consider it the most important thing and run such tests on a regular.&lt;/p>
&lt;p>I’m going to tell you a story about how we conducted the first stage of load testing for one of the initial versions. We installed the system on developer’s laptop PC, engaged the load, and got 4 thousand transactions per second. Not bad for a laptop. Then we installed it on a virtual loading test bench comprised of four servers with performance lower than in production. Made a minimum deployment. After launch, we saw that the result was worse than on the laptop in one thread. It was a shock.&lt;/p>
&lt;p>Really discouraging. A check of loads on servers showed that they were idle.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2013" height="1052" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/09_hu_dd9869293adec9db.webp 768w, https://percona.community/blog/2020/06/09_hu_ae783d62f843e529.webp 480w, https://percona.community/blog/2020/06/09_hu_2353f64853d0f02c.webp 1400w" src="https://percona.community/blog/2020/06/09_hu_2353f64853d0f02c.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;p>We called the developers, and they told us, people from Java world, that there is only one transaction processor thread in Tarantool. It can be effectively used by only one CPU core under load. With this in mind, we then deployed the maximum possible Tarantool instances on each server, engaged load, and got 14.5 thousand transactions per second.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1459" height="1052" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/10_hu_d48feb48eeeed1cd.webp 768w, https://percona.community/blog/2020/06/10_hu_2bf18379730531b.webp 480w, https://percona.community/blog/2020/06/10_hu_e24692d1d403009e.webp 1400w" src="https://percona.community/blog/2020/06/10_hu_e24692d1d403009e.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="967" height="435" sizes="(max-width: 967px) 100vw, 967px" srcset="https://percona.community/blog/2020/06/11_hu_ac10221f74fd516e.webp 768w, https://percona.community/blog/2020/06/11_hu_29dd9cb5de5b941d.webp 480w, https://percona.community/blog/2020/06/11_hu_b6271d25da10ff8d.webp 967w" src="https://percona.community/blog/2020/06/11_hu_b6271d25da10ff8d.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Let me explain one more time. Due to the split-up into roles that use resources differently, our roles responsible for connections processing and data conversion loaded only the CPU, and strictly in proportion to the load.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="930" height="349" sizes="(max-width: 930px) 100vw, 930px" srcset="https://percona.community/blog/2020/06/12_hu_74e47c5ede7c4c4b.webp 768w, https://percona.community/blog/2020/06/12_hu_a4a845ea16e8e80a.webp 480w, https://percona.community/blog/2020/06/12_hu_17d24fa622f697c3.webp 930w" src="https://percona.community/blog/2020/06/12_hu_17d24fa622f697c3.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Meanwhile, memory was used only for processing inbound connections and transient objects.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="972" height="346" sizes="(max-width: 972px) 100vw, 972px" srcset="https://percona.community/blog/2020/06/13_hu_66db3115342e6806.webp 768w, https://percona.community/blog/2020/06/13_hu_cef5033c96d9d7fc.webp 480w, https://percona.community/blog/2020/06/13_hu_a67f99e773b71dd3.webp 972w" src="https://percona.community/blog/2020/06/13_hu_a67f99e773b71dd3.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The situation was opposite for storage servers: CPU load was growing, but much slower than for the servers doing connection processing.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="945" height="348" sizes="(max-width: 945px) 100vw, 945px" srcset="https://percona.community/blog/2020/06/14_hu_e10de5ac07f58da5.webp 768w, https://percona.community/blog/2020/06/14_hu_1c76e4ac038f32d9.webp 480w, https://percona.community/blog/2020/06/14_hu_389f5f2af8cbfc4a.webp 945w" src="https://percona.community/blog/2020/06/14_hu_389f5f2af8cbfc4a.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Memory usage was growing in direct proportion to the amount of data being loaded.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="947" height="355" sizes="(max-width: 947px) 100vw, 947px" srcset="https://percona.community/blog/2020/06/15_hu_d1f1277ae474413d.webp 768w, https://percona.community/blog/2020/06/15_hu_b3887c923059005f.webp 480w, https://percona.community/blog/2020/06/15_hu_74c5f354255ec641.webp 947w" src="https://percona.community/blog/2020/06/15_hu_74c5f354255ec641.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;h2 id="services">Services&lt;/h2>
&lt;p>To develop our new product exactly as an application platform, we made a component for deploying services and libraries on it.&lt;/p>
&lt;p>Services are not just small pieces of code that handle some fields. They can be rather big and complex structures that form a part of a cluster, check reference data, turn over the business logic, and provide responses. The scheme of the service is also exported to GraphQL, and the user gets a one-stop point of access to data, with introspection across the whole model. Quite handy.&lt;/p>
&lt;p>Since services include many more functions, we decided that there should be some libraries where we would keep frequently used code. We added those to a safe environment, having verified that nothing is broken as a result. Now we could assign to functions additional environments in the form of libraries.&lt;/p>
&lt;p>We wanted to have a platform for both storing data and computing. Since we had a whole lot of replicas and shards, we implemented a semblance of distributed computing and named it “map-reduce”, because it was looking like the original map-reduce.&lt;/p>
&lt;h2 id="legacy-systems">Legacy systems&lt;/h2>
&lt;p>Not all of our legacy systems can call us via HTTP and use GraphQL, although they support it. That is why we made a tool enabling data replication to those systems.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="744" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/16_hu_d1e740c38ec8b429.webp 768w, https://percona.community/blog/2020/06/16_hu_6b126d8f1c651214.webp 480w, https://percona.community/blog/2020/06/16_hu_709a82aca473d38b.webp 1400w" src="https://percona.community/blog/2020/06/16_hu_709a82aca473d38b.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If something changes in our systems, some triggers operate in the Storage role, and a message with the changes gets to the processing queue. The message is sent to an external system via a separate replicator role. This role doesn’t store status.&lt;/p>
&lt;h2 id="new-modifications">New modifications&lt;/h2>
&lt;p>As you remember, we made an asynchronous write from a business perspective. But then we realized that it won’t be enough, because there is a class of systems which need to receive a response with operation status right away. So we extended our GraphQL and added mutations. They fit into the existing data handling paradigm quite naturally. In our systems, it is a single reading and writing point for another class of systems.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1682" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/17_hu_2a6b61a9ed64e850.webp 768w, https://percona.community/blog/2020/06/17_hu_34014b08792fac30.webp 480w, https://percona.community/blog/2020/06/17_hu_6bb3e4f211615f09.webp 1400w" src="https://percona.community/blog/2020/06/17_hu_6bb3e4f211615f09.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We also realized that services alone won’t be enough for us, because there can be rather heavy reports which need to be built daily, weekly, and monthly. It may take longer, and the reports can even block Tarantool’s event loop. That is why we set up separate roles: scheduler and runner. Runners don’t store status. They run heavy tasks that we cannot read on the fly. As to the scheduler role, it supervises the launch schedule for those tasks, which is specified in the configuration. The tasks themselves are stored in the same place as business data. When the time is right, the scheduler takes a task, gives it to a runner, the runner calculates it, and saves the result.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/18_hu_7f57c3ebc0d587e5.webp 768w, https://percona.community/blog/2020/06/18_hu_8c90d62b7c885179.webp 480w, https://percona.community/blog/2020/06/18_hu_be604c060ea13b1d.webp 1400w" src="https://percona.community/blog/2020/06/18_hu_be604c060ea13b1d.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Not all tasks are to be run according to schedule. Some need to be calculated on demand. As soon as such a query comes, a task is generated in the sandbox and sent to a runner for execution. After some time, the user asynchronously receives a response telling that calculation is complete and the report is ready.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/19_hu_b5a3cf429fb4474b.webp 768w, https://percona.community/blog/2020/06/19_hu_d34dcf59ae9bb0e1.webp 480w, https://percona.community/blog/2020/06/19_hu_8b288fcdae394fa.webp 1400w" src="https://percona.community/blog/2020/06/19_hu_8b288fcdae394fa.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Initially, we adhered to the paradigm of saving all data by versioning and not deleting it. But in real life, we still need to delete something from time to time, such as, basically, some raw data or temporary information. On the basis of expirations, we made a tool for cleaning the storage from obsolete data.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/20_hu_4600121726131ebf.webp 768w, https://percona.community/blog/2020/06/20_hu_3e1fe0e552b2fd48.webp 480w, https://percona.community/blog/2020/06/20_hu_549cd8e3ad507fef.webp 1400w" src="https://percona.community/blog/2020/06/20_hu_549cd8e3ad507fef.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We also realize that, sooner or later, there will be a situation where there is not enough storage space, but the data still needs to be stored. For this purpose, we’re going to make disk storage soon.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1675" height="726" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/21_hu_993f485b01f73862.webp 768w, https://percona.community/blog/2020/06/21_hu_6c104bded9724d7d.webp 480w, https://percona.community/blog/2020/06/21_hu_bb059319b4be26c1.webp 1400w" src="https://percona.community/blog/2020/06/21_hu_bb059319b4be26c1.webp" alt=" " loading="lazy" />&lt;/figure>  &lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>We started with the objective of loading data to a single model and spent three months developing it. We had six data supplier systems. The whole code for transformation to a single model is around 30 strings in Lua. The greater part of the work is yet to be done. Sometimes, there’s a lack of motivation in adjacent teams, and there are many circumstances making the work more difficult. If you ever face a similar objective, then the time you think will be enough to achieve it should be multiplied by three, or even four.&lt;/p>
&lt;p>Also, remember that existing issues with business processes cannot be resolved using a new data management system, even a high-performance one. What do I mean by this? At the start of our project, we made the customer believe that everything would run like a clockwork once we bring in a new fast database. Processes would run faster, and everything would be OK. In fact, technology cannot resolve all issues that occur in business processes, because business processes are about people. It is people that you should deal with, not technology.&lt;/p>
&lt;p>Development through testing at an early stage may be a headache, and it may take very long. But the benefit will be sensible even in the short term when you have to do nothing to conduct regression testing.&lt;/p>
&lt;p>It is essential to run load tests at all development stages. The earlier you find a fault in architecture, the easier it will be to correct it, saving you a lot of time in the future.&lt;/p>
&lt;p>There is nothing bad in Lua. Everyone can learn to write in it: a Java developer, a JavaScript developer, a Python developer, a front-ender, or a back-ender. We have even analysts writing in it.&lt;/p>
&lt;p>When we tell people that we don’t have SQL, it makes them scared. “How do you retrieve data without SQL? Is it possible?” Sure. There’s no need for SQL in an OLTP class system. There is an alternative in the form of a language that returns to you a document-oriented view. GraphQL, for example. Another alternative is distributed computing.&lt;/p>
&lt;p>If you realize that you will have to scale up or down, then you should, at the very beginning, design your Tarantool-based solution so that it’s able to operate in parallel with tens of Tarantool instances. If you don’t, you are going to face difficulties and pain at a later stage, because Tarantool can use only one CPU core effectively.&lt;/p></content:encoded><author>Vladimir Drynkin</author><category>Advanced Level</category><category>DevOps</category><category>Information</category><category>Open Source Databases</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/image3-1_hu_dd68d9284a99a52d.jpg"/><media:content url="https://percona.community/blog/2020/06/image3-1_hu_a5748d1b4f919475.jpg" medium="image"/></item><item><title>Percona Live ONLINE Opening Keynote: State of Open Source Databases by Peter Zaitsev</title><link>https://percona.community/blog/2020/06/15/percona-live-online-opening-keynote-state-of-open-source-databases-by-peter-zaitsev/</link><guid>https://percona.community/blog/2020/06/15/percona-live-online-opening-keynote-state-of-open-source-databases-by-peter-zaitsev/</guid><pubDate>Mon, 15 Jun 2020 15:44:28 UTC</pubDate><description>Peter Zaitsev is CEO and co-founder of Percona. He opened Percona Live ONLINE with a keynote which took a look at the historical foundations of open source software and how they have shaped the field today.</description><content:encoded>&lt;p>Peter Zaitsev is CEO and co-founder of Percona. He opened &lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live ONLINE&lt;/a> with a keynote which took a look at the historical foundations of open source software and how they have shaped the field today.&lt;/p>
&lt;h2 id="the-history-of-open-source-software">The history of open source software&lt;/h2>
&lt;p>In the early days of computing, software and hardware were bundled together. While the term open source wasn’t coined, software was open by default. According to Peter:  “One of the big reasons for that was copyrights on software was not a thing, because the software was not really a thing before that. Laws tend to move slower and only kind of catch up with technological development.”&lt;/p>
&lt;p>The source code for software was shipped with early hardware. Early adopters - typically from universities - would modify the code to fix bugs and add needed functionality, akin to the advanced open source users of today. The changes back then were openly shared under academic principles.&lt;/p>
&lt;h2 id="enter-antitrust-in-the-1970s">Enter antitrust in the 1970s&lt;/h2>
&lt;p>In the late 1960s and the early 1970s, computing was growing into a significant industry, where IBM was controlling the vast majority of the mainframe market. This resulted in an antitrust lawsuit against IBM in the US, who as a response unbundled software from hardware.&lt;/p>
&lt;p>The Copyright Act was moved by Congress to make software copyrightable and created a separate software industry distinct from hardware. Software becomes the major class of intellectual property.&lt;/p>
&lt;h2 id="the-1980s-and-1990s-the-era-of-romantic-open-source-and-free-software">The 1980s and 1990s: The Era of Romantic Open source (and free) software&lt;/h2>
&lt;p>After the development of copyright for software, new projects started that rejected applying copyright and restrictive licenses to their development. Peter asserted: “I would call that an era of romantic open source software. Right? Because a lot of software was started by hobbyists or as according to Linus Torvald ‘just for fun.’”&lt;/p>
&lt;h2 id="the-2000s-a-dramatic-decade-for-oss">The 2000s: A dramatic decade for OSS&lt;/h2>
&lt;p>The 2000s was a dramatic decade for open source software, part in response to the .com crash. “A lot of companies needed ways to build their solutions very efficiently and Linux, Apache MySQL, a lot of other open source options allowed them to do just that,” said Peter.&lt;/p>
&lt;p>Prior to 2000, big OSS companies were limited to Red Hat which went through an IPO in the late 1990s. Enter the 2000s and Sun acquired MySQL for $1 billion, which was hugely significant to the OSS market. It was during this period that Steve Ballmer famously asserted, “Linux is a cancer that attaches itself in an intellectual property sense to everything it touches.”&lt;/p>
&lt;p>In the 2000s, many businesses started to recognize the value of open source software, and with an increasing number of large enterprises starting to adopt the open-source first mentality. This included adoption by governments “to help them avoid reliance on companies from other countries,” according to Peter.&lt;/p>
&lt;p>The use of open source software had a range of benefits for both companies and for developers as individuals. For enterprise customers, moving to open source resulted in lower direct costs both short term and long term. As for developers, using open source became the preference for many of them, as it was easier to experiment and get familiar with tools. Over time, it became easier to find developers that were proficient in open source technologies compared to proprietary software. This led to better productivity and faster innovation. Customers were also able to avoid the historical barrier of vendor lock-in.&lt;/p>
&lt;p>The decade then led to a new generation of open source companies being created. However, the fact that many of these were venture capital funded lead to the need for fast, high returns on those investments. Thus, many of these companies found they had the need to build a monopoly based on the pervasive message as to the advantages of open source while also increasing “stickiness” for their own businesses.&lt;/p>
&lt;h2 id="romantic-vs-business-values-lead-to-not-quite-open-source">Romantic vs business values lead to ’not quite open source’&lt;/h2>
&lt;p>For Peter, the time of new open source companies is a new challenge. “If you really look at those approaches to business values, many are in conflict with the early stage of romantic open-source software, and the values and ideas about sharing and letting other people innovate on your software, because hey, that actually can create competition for you,” he explained.&lt;/p>
&lt;p>A lot of business models were evolving from open source to ’not quite open-source’. Some of those models would be open source eventually, such as shared source licenses and open-source compatible software, which is used by a lot of cloud vendors. Peter noted that vendors would spruik this by saying, “You can move from open source to our open-source compatible software. You probably would have a very hard time moving back, but we don’t talk about that.”&lt;/p>
&lt;p>On the positive side, the availability of funding meant there were a lot of investments and a high pace of innovation in the software around the open source community. On the negative side, the market became more complicated with the challenge to differentiate between open source software and ‘not quite open’ software that didn’t provide the same value of truly open source software.&lt;/p>
&lt;h2 id="the-2010s-the-rise-of-the-cloud-unique-challenges-and-opportunities-for-oss">The 2010s: The rise of the cloud: unique challenges and opportunities for OSS&lt;/h2>
&lt;p>While AWS was started in the previous decade, the 2010s were critical for open source databases - specifically, around the cloud and open source. Peter asserted, “Cloud really hijacked the GPL license. Before the Software as a Service deployment model, software vendors who did not want others to build commercial software on their solutions could just use the GPL. Not anymore. Now, AWS probably makes more money on MySQL than Oracle does. And they can just use the GPL software and don’t have to pay Oracle anything.”&lt;/p>
&lt;p>Unlike the 1970s, cloud services are now bundling hardware usage costs with software. This meant open source software could no longer benefit from a zero price effect.&lt;/p>
&lt;p>This was important psychology, as Peter noted: “Previously I would have to buy a server separately. And then I have a choice, either I could go and pay thousands of dollars to license Oracle to run on that server, or I could go ahead and download Postgres and use it for free.  That is not the case anymore. It just becomes a case of a difference in the price which may not be very well understood.”&lt;/p>
&lt;h2 id="market-acceptance-of-not-fully-open-source-software-models">Market acceptance of Not fully open source software models&lt;/h2>
&lt;p>Peter asserted that acceptance of not fully Open Source Software models is on the rise. “It’s very important for us as an open source database community to really educate folks in the market about the difference of an open source software offering and one which is marketed using an open source term but not providing the true values of open source software.”&lt;/p>
&lt;h2 id="2020s-great-momentum-for-commercial-open-source">2020s: Great Momentum for Commercial Open Source&lt;/h2>
&lt;p>It’s a fantastic time for Commercial Open Source, with many companies getting billion dollar valuations:&lt;/p>
&lt;ul>
&lt;li>RedHat - $24B (acquired by IBM)&lt;/li>
&lt;li>MongoDB - $11.2B (current valuation)&lt;/li>
&lt;li>GitHub - $7.5B (acquired by Microsoft)&lt;/li>
&lt;li>Databricks - $6.2B (current valuation)&lt;/li>
&lt;li>Elastic - $5.8B (current valuation)&lt;/li>
&lt;li>Hashicorp - $5B (current valuation)&lt;/li>
&lt;li>Confluent - $4.5B (current valuation)&lt;/li>
&lt;li>Cloudera - $2.5B  (current valuation)&lt;/li>
&lt;/ul>
&lt;p>Peter commented, “Because of the success of MongoDB, Elastic and some other open source companies, we see a lot of investment and a lot of innovation in the Open Source Database space.” This includes new technologies like Planet-Scale, InfluxDB, yugabyteDB, and others. It’s not limited to relational databases, it includes multimodal cloud, graph databases, and time series focused.&lt;/p>
&lt;h2 id="covid-19-pandemic">COVID-19 Pandemic&lt;/h2>
&lt;p>The pandemic has led to an acceleration of digital transformation including service delivery online and digital education. This requires lower costs and/or cost-cutting due to the predicted economic slowdown. This can be another reason for open source success, as companies have to innovate and keep their costs down. These two desires will encourage companies to both consider open source, and to keep a close key on the cost for running those systems whether this is on existing hardware or in the cloud.&lt;/p>
&lt;h2 id="dbaas">DBaaS&lt;/h2>
&lt;p>Today database as a service (DBaaS) is a preferred way to consume open source database software. According to Peter, “This allows the development team to use multiple database technologies more easily, matching them to application needs because they don’t really need to install and maintain them.”&lt;/p>
&lt;p>However, Peter did point to one problem around DBaaS that can affect the success of implementation for companies and for developer teams. For many use cases, DBaaS is commonly marketed by cloud vendors as ‘fully managed.’ “Because of that, we don’t have to get any DBAs or other database experts on the team. However this ‘fully managed’ approach still needs to be configured for security, somebody still needs to advise us on the schema, help us to design the queries, etc,” explained Peter.&lt;/p>
&lt;p>The rise of DBaaS has meant that developers can choose and use databases directly without the supervision of database professionals. This can cause various bad outcomes ranging from security leaks to very inefficient delivery of database services over time. For developers that assume their DBaaS provider will deliver more insights or advice, this can lead to wasted time and budget.&lt;/p>
&lt;h2 id="dbaas-and-multiverse">DBaaS and Multiverse&lt;/h2>
&lt;p>Peter then provided an overview of the future as he sees it: “From an open source prism, you can think of the cloud as a commodity with many compatible implementations. Or think about highly differentiated clouds, where you have proprietary solutions available from a single vendor. The latter can be a huge vendor lock-in.  However, many are trying to avoid lock-in.”&lt;/p>
&lt;p>Thus, he said, we are increasingly seeing multiple database technologies: multiple environments, hybrid cloud, multi-cloud, Many proprietary solutions are available around cloud and hybrid environments, like Google Anthos, VMware and AWS Outposts.  Simultaneously Kubernetes also has emerged as the leading open source API for hybrid and public clouds.&lt;/p>
&lt;p>Kubernetes is ubiquitous. There are proprietary solutions to simplify Kubernetes management, and the Kubernetes interface is supported by Multi and Hybrid Cloud Platforms. The is relevant to open source databases and Peter believes we should be focusing on:&lt;/p>
&lt;ul>
&lt;li>Adapting Cloud Native deployments in Multi and Hybrid Cloud&lt;/li>
&lt;li>Kubernetes as the API of choice for Open Source database deployments&lt;/li>
&lt;li>Making things simple and comparable to integrated DBaaS Solutions&lt;/li>
&lt;/ul>
&lt;p>An important question to ask is: “If I am choosing DBaaS as my software consumption model, how do I get the most value from what Open Source Software provides?”&lt;/p>
&lt;p>According to Peter, Percona is embracing the cloud-native and multi-cloud approach through Kubernetes. Percona has released &lt;a href="https://www.percona.com/doc/kubernetes-operator-for-pxc/index.html" target="_blank" rel="noopener noreferrer">Kubernetes Operator for  XtraDB Cluster&lt;/a> and &lt;a href="https://www.percona.com/doc/kubernetes-operator-for-psmongodb/index.html" target="_blank" rel="noopener noreferrer"> Kubernetes Operator for Percona Server for MongoDB&lt;/a>. “We are also working through &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitor and Management&lt;/a> to really help you to reduce the friction and run the open source database successfully in those cloud environments and on-premises,” he said.&lt;/p>
&lt;p>Peter also advised attendees to take the time to fill out the &lt;a href="https://www.percona.com/blog/2020/03/31/share-your-database-market-insight-by-completing-perconas-annual-survey/" target="_blank" rel="noopener noreferrer">Open Source Data Management Survey&lt;/a>. Peter closed the keynote with: “Finally, I want to say Happy 25th Birthday to MySQL. Great job, MySQL team!”&lt;/p>
&lt;p>You can also watch Peter’s &lt;a href="https://www.percona.com/resources/videos/state-open-source-database-plo2020" target="_blank" rel="noopener noreferrer">keynote&lt;/a>.&lt;/p></content:encoded><author>Cate Lawrence</author><category>AWS</category><category>DBaaS</category><category>Kubernetes</category><category>Kubernetes</category><category>MariaDB</category><category>MongoDB</category><category>MongoDB</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>opensource</category><category>Percona</category><category>PostgreSQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_1395b6e2186771a6.jpg"/><media:content url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_c0e4c47b55fa22a9.jpg" medium="image"/></item><item><title>Matt Yonkovit: It's a crazy world, and these trends are disrupting and breaking your database infrastructure</title><link>https://percona.community/blog/2020/06/12/matt-yonkovit-its-a-crazy-world-and-these-trends-are-disrupting-and-breaking-your-database-infrastructure/</link><guid>https://percona.community/blog/2020/06/12/matt-yonkovit-its-a-crazy-world-and-these-trends-are-disrupting-and-breaking-your-database-infrastructure/</guid><pubDate>Fri, 12 Jun 2020 15:30:28 UTC</pubDate><description>Matt Yonkovit, Chief Experience Officer at Percona, presented a session at this year’s Percona Live ONLINE, sharing initial insights from the Open Source Data Management Survey 2020. The survey provides a critical insight first-hand into how enterprises of all sizes are using, developing, and troubleshooting open source database software. The full data will be released later this year with a detailed analysis.</description><content:encoded>&lt;p>Matt Yonkovit, Chief Experience Officer at Percona, presented a session at this year’s Percona Live ONLINE, sharing initial insights from the &lt;a href="https://www.percona.com/open-source-data-management-software-survey" target="_blank" rel="noopener noreferrer">Open Source Data Management Survey 2020.&lt;/a> The survey provides a critical insight first-hand into how enterprises of all sizes are using, developing, and troubleshooting open source database software. The full data will be released later this year with a detailed analysis.&lt;/p>
&lt;h2 id="he-who-controls-the-application-controls-the-stack">He who controls the application controls the stack&lt;/h2>
&lt;p>Matt started with discussing the challenge that developers face: “Those building it are not the ones managing it. And those who are building it are the ones deciding what to put in it.”&lt;/p>
&lt;p>Last year a survey asked Who gets to choose the database technology at companies? Most people choosing database technology are outside the database or the infrastructure side. More architects (32%) and developers (26%) are choosing the tech than management (17%) or DBAs (23%).&lt;/p>
&lt;p>However, the challenge is that the DBAs are inheriting technology from the development stack, and all of a sudden they have to support it. Matt said he likes to call this, “The technology inheritance problem: So now you’ve got a team of people who are not necessarily skilled at managing those technologies all of a sudden being responsible for new technologies.”&lt;/p>
&lt;h2 id="the-multiverse-of-technology">The multiverse of technology&lt;/h2>
&lt;p>Enter the multiverse of technology: multi-database, multi-cloud, multi-location, multi-skilled. Matt explained this as follows:&lt;/p>
&lt;p>“Instead of saying we’re going to run on AWS and we’re going to consolidate on a single database or a set of databases, you’re running on multiple databases, you’re running in multiple locations, you’re running multi skilled people, because you’re no longer, you know, an expert Oracle DBA on its own. You’re a DBA of everything. And it’s leading to these multi-database environments.”&lt;/p>
&lt;h2 id="the-database-footprint-is-growing">The database footprint is growing&lt;/h2>
&lt;p>In last year’s survey, more than 92% of companies were running more than one database, and 89% have more than one open source database in place. This year the number of companies that reported having between 100 and 1000 database instances in place grew by 40%. Those reporting over 1000 database instances grew by more than 50%. Matt noted:&lt;/p>
&lt;p>“Now we’ve got environments that have thousands of databases that have to be managed and supported, and that means that the care and feeding of each database is very difficult.”&lt;/p>
&lt;p>This is partly attributable to new technologies like machine learning and an insatiable need for more data to make better decisions. The footprints of databases continue to grow. Only 3.5% shrunk, whereas 14% stayed the same. And the vast majority, 80% saw growth, and almost 39% saw larger massive growth in the size of their environment.&lt;/p>
&lt;h2 id="enter-the-multiverse">Enter the multiverse&lt;/h2>
&lt;p>The deluge of data and more databases leads to a multi-cloud space. In 2019, 30% reported that they were running a multi-cloud environment. In 2020 it’s 39%. Matt noted this by saying, “Some of the cloud providers are now taking notice. They’re investing in tools to let you run their platform across other competitors’ platforms. The growth also exists, albeit slower in the hybrid space: In 2019, 41% were hybrid, and in 2020 it’s 44%.&lt;/p>
&lt;p>So we’re seeing more databases, more data, more providers, more locations, more hybrid installations. And so, what are the consequences? “It means for a lot of us who have to work on these systems, we have less expertise in any one of them, because we don’t have the time to not only enhance our skills but to enhance the systems that we’re supporting and ensure that they’re properly managed and set up. We’ve less time per application, and we just have less time available,” continued Matt.&lt;/p>
&lt;p>This means more mistakes are happening, more automated cascading issues, more outages, more security issues, more complexity, more cost, and more help is needed.&lt;/p>
&lt;h2 id="how-does-the-industry-respond">How does the industry respond?&lt;/h2>
&lt;p>Matt asserted: “There’s a pervasive debate between, ‘Do we need to automate? Or how much do we need to automate? How much do we not need people? How much do we need to focus on, the automation of things, and the AI versus bringing in experts?’ We are looking at DBaaS versus the need for DBAs, and we still need experts and people who know what they are doing.”&lt;/p>
&lt;p>“We need to ensure that we still have the tools and the skill set to address these problems as they occur correctly. Otherwise, we just make more problems.”&lt;/p>
&lt;h2 id="dbaas">DBaaS&lt;/h2>
&lt;p>According to Matt: “Database as a service (DBaaS) is probably one of the best inventions that have happened in the last ten years to databases.” It enables developers to move quicker; it overcomes all kinds of skill gaps. However, it doesn’t eliminate the need for understanding and the tools to help. It helps, but it does not eliminate the need for DBAs and expertise.&lt;/p>
&lt;h2 id="what-keeps-you-up-at-night">What keeps you up at night?&lt;/h2>
&lt;p>According to the respondents of this year’s survey, particular challenges keep developers up at night:&lt;/p>
&lt;p>The biggest is downtime (31%) followed by fixing some unforeseen issues (17%), security issues 15%). Bad performance and query issues are insomnia inducing for 13%, while staffing issues/a lack of resources challenge 9% of respondents.&lt;/p>
&lt;h2 id="problems-happen-everywhere">Problems happen everywhere&lt;/h2>
&lt;p>The survey further found that problems happen everywhere, whether you’re in the cloud or not:&lt;/p>
&lt;p>62% in the cloud had performance issues, 54% non-cloud. Overworked staff increase by 10% when DBaaS is factored in, from 19% to 29%. According to Matt: “My speculation is when we move to a database service, we move those resources to do other things. And when database problems occur, they’ve got 17 other jobs to work on.”&lt;/p>
&lt;h2 id="configuration-errors-a-significant-cause-of-data-breaches">Configuration errors a significant cause of data breaches&lt;/h2>
&lt;p>Outages and slowdowns persist in being a headline-grabbing problem. &lt;a href="https://www.cisomag.com/db8151dd-an-untraceable-data-breach-22-mn-emails-compromised/" target="_blank" rel="noopener noreferrer">News this week&lt;/a> reported the hacking of an open Elasticsearch database containing around 22 million of email records. &lt;a href="https://enterprise.verizon.com/resources/reports/dbir/" target="_blank" rel="noopener noreferrer">Research&lt;/a> by Verizon reveals that the fastest growing data breach cause is configuration errors.&lt;/p>
&lt;h2 id="how-many-people-choose-to-scale-their-database-via-credit-card">How many people choose to scale their database via credit card?&lt;/h2>
&lt;p>From a spend perspective, survey respondents were asked: are you spending at plan, below plan, or above plan? About 24% were above plan. 33% of those using DBaaS and Cloud were above plan.&lt;/p>
&lt;p>Upon being asked, how often have you had to upgrade your database instances to something bigger the results are significant:&lt;/p>
&lt;ul>
&lt;li>0 times - 11%&lt;/li>
&lt;li>1-3 times 40.4%&lt;/li>
&lt;li>4-9 times 28.6%&lt;/li>
&lt;li>10+ times 19.5%&lt;/li>
&lt;/ul>
&lt;p>Matt stated that he believes the following situation is more common than it should be: “Most of these can be avoided by fixing performance problems. If we don’t look for those performance issues, then we’re going to fix them by paying more. And that’s what a lot of people end up doing.”&lt;/p>
&lt;h2 id="unexpected-costs">Unexpected costs&lt;/h2>
&lt;p>Several survey respondents have experienced unexpected costs, which have increased as the software complexity increases:&lt;/p>
&lt;ul>
&lt;li>Non-public cloud users - 8% reported unexpected costs.&lt;/li>
&lt;li>Public cloud users - 10% reported unexpected costs&lt;/li>
&lt;li>Public cloud DBaaS - 19% said that their costs were unexpectedly higher&lt;/li>
&lt;/ul>
&lt;p>“We need better automation, and we need smarter tools, we need better education, better security, better performance, we need to make us all more efficient and be able to solve these problems that come up. It’s very, very important,” commented Matt.&lt;/p>
&lt;h2 id="percona-monitoring-and-management">Percona Monitoring and Management&lt;/h2>
&lt;p>&lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management&lt;/a> is the company’s free and open source tool to simplify this with a single interface to reduce complexity. Matt shared this as background: “We want a simplified management system, where we can take that complexity and give you the ability to reduce the complexity with it.”&lt;/p>
&lt;h2 id="matts-selfish-security-goal-and-a-simple-solution">Matt’s selfish security goal and a simple solution&lt;/h2>
&lt;p>When discussing databases and security, Matt provided a very personal goal for improving the current situation. He lamented, “I don’t need more credit monitoring in response to database breaching, I am good until the year 2082!”&lt;/p>
&lt;p>Matt has a simple solution: “I can solve more than 50% of the data breaches that exist now. And I can do it in one line of code: Set your password! db.changeUserPassword (username, password). It is the Change Password command for MongoDB. Mongo and Elastic are currently the two most breached databases. Most of those breaches are because nobody set a password!”&lt;/p>
&lt;p>Percona Monitoring and Management 2.6 includes the first version of Percona’s security threat tool:&lt;/p>
&lt;ul>
&lt;li>This provides checks for basic security problems and the most common issues, like missing passwords or not being at the latest version&lt;/li>
&lt;li>More checks will be added over the next several months&lt;/li>
&lt;/ul>
&lt;h3 id="the-first-release-of-percona-distribution-for-mongodb">The first Release of &lt;a href="https://www.percona.com/software/mongodb" target="_blank" rel="noopener noreferrer">Percona Distribution for MongoDB:&lt;/a>&lt;/h3>
&lt;p>On the first Distribution that Percona has released for MongoDB, Matt shared: “We take all the best of the open-source components and bundle it into one there. And we’re also now offering &lt;a href="https://www.percona.com/services/managed-services/percona-managed-database-services" target="_blank" rel="noopener noreferrer">managed services for MongoDB&lt;/a>.”&lt;/p>
&lt;p>Percona also has a Distribution for PostgreSQL currently, with a Distribution for MySQL coming up. Matt also mentioned the world’s highest, most trusted high availability solution for MySQL in &lt;a href="https://www.percona.com/software/mysql-database/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">PerconaXtraDB Cluster&lt;/a>.&lt;/p>
&lt;p>Matt described Percona’s approach as looking to remove the problems for companies running multiple databases: “We take out all of those features and fixes and bundle it on top of MySQL Community to make it truly an enterprise-ready system.”&lt;/p>
&lt;h2 id="helping-you-to-scale-and-simplify">Helping you to scale and simplify:&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/software/mysql-database/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">XtraDB Cluster 8&lt;/a> is faster and more scalable. There are new Kubernetes operators with easier management.&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/software/postgresql-distribution" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL&lt;/a> has launched with more performance enhancements to come.&lt;/li>
&lt;/ul>
&lt;h2 id="percona-and-linode-partnership">Percona and Linode Partnership&lt;/h2>
&lt;p>At the end of the session Matt went through how Percona is partnering with Linode to help bring Linode’s customers an enhanced DBaaS. The community benefits from better operations, better tools, and enhancements that will show up in our distributions.&lt;/p>
&lt;p>Blair Lyon, VP of Marketing at Linode joined the session to share how he sees this developing:&lt;/p>
&lt;p>“Since 2003, Linode has been helping our clients accelerate innovation by making cloud computing simple, affordable, and accessible for all. We’re leading a growing category of alternative cloud providers with nearly a million worldwide customers and 11 global data centers. And the key to being a true alternative to the big guys is providing the best of breed enterprise solutions and DBaaS is no exception.”&lt;/p>
&lt;p>Finally, Matt encouraged all attendees to the Percona Live event to provide their insight as part of 2020’s Open Source Data Management research report. If you have not filled out the &lt;a href="https://www.percona.com/blog/2020/03/31/share-your-database-market-insight-by-completing-perconas-annual-survey/" target="_blank" rel="noopener noreferrer">Open Source Data Management Survey&lt;/a> then you can do so. Watch Matt’s &lt;a href="https://www.percona.com/resources/videos/trends-are-disrupting-and-breaking-your-db-infrastructure-matt-yonkovit-percona" target="_blank" rel="noopener noreferrer">keynote&lt;/a>.&lt;/p></content:encoded><author>Cate Lawrence</author><category>Cloud</category><category>DBA Tools</category><category>DBaaS</category><category>Kubernetes</category><category>Kubernetes</category><category>MongoDB</category><category>MongoDB</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Percona Monitoring and Management</category><category>PMM</category><category>Postgres</category><category>PostgreSQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/PLO-Card-Matt_hu_b48ba47ad5468a2d.jpg"/><media:content url="https://percona.community/blog/2020/06/PLO-Card-Matt_hu_b4dc25bdfce8547c.jpg" medium="image"/></item><item><title>Percona Live ONLINE: Anti-cheating tools for massive multiplayer games using Amazon Aurora and Amazon ML services</title><link>https://percona.community/blog/2020/06/11/percona-live-online-anti-cheating-tools-for-massive-multiplayer-games-using-amazon-aurora-and-amazon-ml-services/</link><guid>https://percona.community/blog/2020/06/11/percona-live-online-anti-cheating-tools-for-massive-multiplayer-games-using-amazon-aurora-and-amazon-ml-services/</guid><pubDate>Thu, 11 Jun 2020 14:21:08 UTC</pubDate><description>Would you play a multiplayer game if you discovered other people are cheating? According to a survey by Irdeto, 60% of online games were negatively impacted by cheaters, and 77% of players said they would stop playing a multiplayer game if they think opponents are cheating. Player churn grows as cheating grows.</description><content:encoded>&lt;p>Would you play a multiplayer game if you discovered other people are cheating? According to a survey by Irdeto, 60% of online games were negatively impacted by cheaters, and 77% of players said they would stop playing a multiplayer game if they think opponents are cheating. Player churn grows as cheating grows.&lt;/p>
&lt;p>Stopping this is therefore essential if you want to build and develop your community, which is essential to success for today’s gaming companies. This session at &lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live ONLINE&lt;/a> was presented by Yahav Biran, specialist solutions architect, gaming technologies at Amazon Web Services, and Yoav Eilat, Senior Product Manager at Amazon Web Services, presented a talk and demonstration about anti-cheating tools in gaming based on using automation and machine learning (ML).&lt;/p>
&lt;p>Yoav notes that while people might think of ML in terms of text or images, but: “There’s a considerable percentage of the world’s data sitting in relational databases. How can your application use it to get results and make predictions?”&lt;/p>
&lt;h2 id="six-steps-for-adding-machine-learning-to-an-application">Six steps for adding Machine Learning to an Application&lt;/h2>
&lt;p>Traditionally there are a lot of steps for adding ML to an application with considerable expertise required and manual work, with the efforts of an application developer, database user and some help from a machine learning database scientist:&lt;/p>
&lt;ol>
&lt;li>Select and train database models&lt;/li>
&lt;li>Write application code to read data from the database&lt;/li>
&lt;li>Format the data for the ML model&lt;/li>
&lt;li>Call a machine learning service to run the ML model on the formatted data&lt;/li>
&lt;li>Format the output for the application&lt;/li>
&lt;li>Load the results to the application&lt;/li>
&lt;/ol>
&lt;p>The result is most machine learning is done offline by a data scientist in a desktop tool. “We would like to be able to add some code to your game and use the models directly from there,” explained Yahav.&lt;/p>
&lt;p>With multiple databases such as the customer service database or order management system, or in the instance of gaming, this would all be a lot of work to do manually. “So, we want to see how we can do that in an easier and automated way,” continued Yahav.&lt;/p>
&lt;h2 id="examples-where-cheating-can-occur">Examples where cheating can occur&lt;/h2>
&lt;p>The duo provided some examples of common cheating behaviour that can occur in games:&lt;/p>
&lt;ul>
&lt;li>Authentication: player authentication in the game, to prove they are who they say they are and that they have the right account&lt;/li>
&lt;li>Transactional data: what the players purchase inside the game, so they either don’t spend funds they don’t have or don’t lose items they purchased legitimately&lt;/li>
&lt;li>Player moves: for example where players in cahoots are walking in front of each other like a human shield&lt;/li>
&lt;/ul>
&lt;p>“Where you have a player that’s walking in one direction, shooting in the other direction and doing five other things at the same time, then it’s probably a bot,” said Yahav.&lt;/p>
&lt;h2 id="demonstrating-ml-in-action">Demonstrating ML in action&lt;/h2>
&lt;p>The demo was built on Amazon Aurora, a relational database offered by AWS and that is compatible with MySQL and PostgreSQL. The database includes some optimizations and performance improvements, plus a few additional features. It has pay as you go pricing.&lt;/p>
&lt;p>As Yahav explains: “The machine learning capabilities added in 2019 allow you to do a query in your Aurora database and then transfer it to a machine learning service for making a prediction. There’s integration with Amazon SageMaker and Amazon Comprehend, which are two machine learning services offered by AWS. The whole thing was done using SQL queries.&lt;/p>
&lt;p>Thus, you don’t need to call API’s; there’s no need to write additional code, you’re doing a step, you can just write a statement where you’re selecting from the results of the machine learning call. You can just use the results like you would use any other data from your database.”&lt;/p>
&lt;h2 id="shortening-the-process-from-six-steps-to-three">Shortening the process from six steps to three&lt;/h2>
&lt;p>Using this approach, the process is now made much simpler:&lt;/p>
&lt;ul>
&lt;li>(Optional) select and configure the ML model with Amazon SageMaker Autopilot&lt;/li>
&lt;li>Run a SQL query to invoke the ML service&lt;/li>
&lt;li>Use the results in the application&lt;/li>
&lt;/ul>
&lt;p>This article focuses on gaming; however, the presentation also provides details about fraud detection in financial transactions, sentiment analysis in the text (such a customer review written on a website), and a classification example to sort customers by predicted spend.&lt;/p>
&lt;h2 id="ml-queries-in-gaming-scenarios">ML queries in gaming scenarios&lt;/h2>
&lt;p>Yahav and Yoav trained a SageMaker model to recognize anomalous user authentication activities such as the wrong password. You can dig deep into the code for the demonstration over at &lt;a href="https://github.com/aws-samples/amazon-aurora-call-to-amazon-sagemaker-sample" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>, so we’ll only walk through some of the code.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1113" height="581" sizes="(max-width: 1113px) 100vw, 1113px" srcset="https://percona.community/blog/2020/06/image6_hu_5b599d1f42b0e428.webp 768w, https://percona.community/blog/2020/06/image6_hu_ad9c924fa52e7cc7.webp 480w, https://percona.community/blog/2020/06/image6_hu_2e286b772e6437f9.webp 1113w" src="https://percona.community/blog/2020/06/image6_hu_2e286b772e6437f9.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The model can also use the function auth_cheat_score to find players with a significant cheat score during authentication.&lt;/p>
&lt;h2 id="introducing-emustarone">Introducing EmuStarOne&lt;/h2>
&lt;p>The game was developed initially in 2018 and is a massively multiplayer online (MMO) game that enables players to fight, build, explore and trade goods with each other.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="631" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image4_hu_6f330074c274c502.webp 768w, https://percona.community/blog/2020/06/image4_hu_59ccacf5a0b2213c.webp 480w, https://percona.community/blog/2020/06/image4_hu_fa1a644466c32137.webp 1280w" src="https://percona.community/blog/2020/06/image4_hu_fa1a644466c32137.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The game can be viewed &lt;a href="https://yahavb.s3-us-west-2.amazonaws.com/EmuStarOne.mp4" target="_blank" rel="noopener noreferrer">https://yahavb.s3-us-west-2.amazonaws.com/EmuStarOne.mp4&lt;/a>&lt;/p>
&lt;p>Players authenticate from supporting clients, suc as a PC or game console.&lt;/p>
&lt;p>Five personality traits and game events define Emulants: they can move, forge, dodge, etc. and they can transact with virtual goods.&lt;/p>
&lt;h2 id="what-does-cheating-look-like-in-the-data">What does cheating look like in the data?&lt;/h2>
&lt;p>To understand what cheating looks like within games, we have to understand what good and bad behaviour looks like in our game data over time:&lt;/p>
&lt;ul>
&lt;li>Players can cheat as they make illegal trades or run bots that manipulate game moves on behalf of other players.&lt;/li>
&lt;li>Cheating can manifest in different ways, such as player move anomalies and consecutive failed login attempts from two different sources.&lt;/li>
&lt;/ul>
&lt;p>In general, ML solutions work very well with problems that are evolving and are not static.&lt;/p>
&lt;h2 id="how-can-we-stop-cheating-in-the-game">How can we stop cheating in the game?&lt;/h2>
&lt;p>To stop cheating requires a plan and some decisions to be made before creating the data model or ML approach:&lt;/p>
&lt;ul>
&lt;li>We can form an anti-cheat team.&lt;/li>
&lt;li>Take action against cheaters e.g., force logout with a hard captcha as a warning.&lt;/li>
&lt;li>Escalate the anti-cheating actions as needed.&lt;/li>
&lt;li>Eventually, cheaters learn the system behavior, so there is also the consideration of false positives.&lt;/li>
&lt;li>Continuously redefine our cheating algorithms.&lt;/li>
&lt;/ul>
&lt;p>What we want to enable by forming this anti-cheat team is to stop those that cheat and continuously refine the algorithm.&lt;/p>
&lt;h2 id="emustar-one-game-data-authentication">EmuStar One game data authentication&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1280" height="584" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image3_hu_3621e134090c5eeb.webp 768w, https://percona.community/blog/2020/06/image3_hu_9c4a37b1b86561f8.webp 480w, https://percona.community/blog/2020/06/image3_hu_3da1ba39984001b7.webp 1280w" src="https://percona.community/blog/2020/06/image3_hu_3da1ba39984001b7.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Yoav explained:&lt;/p>
&lt;p>“In the first data set, we have the player authentication; this is the authentication transaction. There is a timestamp that the player came, and in this case, the authentication method was the Xbox Live token.”&lt;/p>
&lt;p>It means that the user logged through to the Xbox Authentication Service. It includes the playerGuid, the user agent, which in this case, is an Xbox device. You can see the source IP and the cidir and the geo-location.&lt;/p>
&lt;h2 id="player-transaction">Player transaction&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1280" height="565" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image7_hu_db7a92cb751e67e0.webp 768w, https://percona.community/blog/2020/06/image7_hu_d52acf500c47eeaf.webp 480w, https://percona.community/blog/2020/06/image7_hu_96eb33395f13811f.webp 1280w" src="https://percona.community/blog/2020/06/image7_hu_96eb33395f13811f.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="the-player-moves">The player moves&lt;/h2>
&lt;p>
&lt;figure>&lt;img width="1280" height="558" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image2_hu_93f1c5c94e739f98.webp 768w, https://percona.community/blog/2020/06/image2_hu_7def03a406a1f029.webp 480w, https://percona.community/blog/2020/06/image2_hu_a78cefa306d8e98d.webp 1280w" src="https://percona.community/blog/2020/06/image2_hu_a78cefa306d8e98d.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The player moves (in this case is the X and Z coordination) include the timestamp and the player. There are three more properties - the quadrant, the sector, and the events can be traversing, user traversing from one place to another, or forging or dodging or other events that the game allowed.&lt;/p>
&lt;h2 id="the-three-ml-models-used-for-game-data">The three ML models used for game data&lt;/h2>
&lt;ul>
&lt;li>For authentication: IP insights is an unsupervised learning algorithm for detecting anomalous behavior and usage patterns of IP addresses&lt;/li>
&lt;li>For transactions: Supervised linear regression - this is because most transactions are already classified by Customer care and player surveys&lt;/li>
&lt;li>For player moves: “Random cut forest (RCF), assuming most player moves are legit so anomalous moves indicate potential cheaters,” explained Yoav.&lt;/li>
&lt;/ul>
&lt;h2 id="data-preparation">Data preparation&lt;/h2>
&lt;p>The game involves a mammoth amount of data. Yoav shared: “We have 700,000 authentication events, 1 million transactions and 65 million player moves. For the supervised data, we classified data between 0.1 to 3% to allow the model to distinguish between legit transactions. Move authentication and other Models were built using Jupyter notebooks hosted by SageMaker. Data was stored on s3.&lt;/p>
&lt;p>“Once that we were able to distill the data and train the model, we deployed the model with hosted inference endpoints using SageMaker as the service. We used Aurora to invoke the endpoints.”&lt;/p>
&lt;h2 id="data-encoding-and-transformation">Data encoding and transformation&lt;/h2>
&lt;p>In general, ML models like numbers - interest, doubles, or floats. So the String attributes were encoded. The same encoding was on the Aurora side, covering for example player move events such as TraverseSector or Travel.Explore&lt;/p>
&lt;p>The notebook is open source so you can see how encoding strings of the player moves was achieved.&lt;/p>
&lt;p>Yoav explained: " I took the quadrant, encoded the sector. encoded the event, and encoded it using the pandas, in the end, and the OneHot encoder.”&lt;/p>
&lt;p>The code for an alternative method for achieving this was also shared:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="800" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image5_hu_140c2bec7ae890f6.webp 768w, https://percona.community/blog/2020/06/image5_hu_1b9296197b8b7fbe.webp 480w, https://percona.community/blog/2020/06/image5_hu_61bb59f2bf0e1262.webp 1280w" src="https://percona.community/blog/2020/06/image5_hu_61bb59f2bf0e1262.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="the-demo">The demo&lt;/h2>
&lt;p>Based on the characteristics of cheating in our game, cheaters are found via:&lt;/p>
&lt;ul>
&lt;li>Looking for suspicious transactions&lt;/li>
&lt;li>Looking for suspicious authentication by the players who executed these transactions&lt;/li>
&lt;li>Then seeing if the player moves were suspicious&lt;/li>
&lt;/ul>
&lt;p>Yahav shared code for the materialized view for authentication, querying the parameters and filtering only the suspicious ones that are mentioned as cls>zero classified as fraudulent.&lt;/p>
&lt;p>An anomaly score cls>2 indicates a suspicious move - the tools are very flexible!&lt;/p>
&lt;p>Yahav then executed a query for “the timestamp and the player Guids that are basically are suspicious.”&lt;/p>
&lt;p>The live demo presented worked to filter suspicious transactions. Then the authentication cheat was joined with the transaction cheat. Subsequently, 13 suspicious cases were revealed based on timestamps. The suspicious moves were then queried based on the timestamps.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1280" height="514" sizes="(max-width: 1280px) 100vw, 1280px" srcset="https://percona.community/blog/2020/06/image1_hu_4d63c538363033f8.webp 768w, https://percona.community/blog/2020/06/image1_hu_d2e99039db57baac.webp 480w, https://percona.community/blog/2020/06/image1_hu_328e3afe8d57367.webp 1280w" src="https://percona.community/blog/2020/06/image1_hu_328e3afe8d57367.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The demo included lots of movements, and transactions from all directions.&lt;/p>
&lt;p>Through exploring the player timestamp, playerGuid, quadrant, and sector of all the suspicious cases, it revealed where suspicious behavior occurred so that monitoring could occur in that specific area.&lt;/p>
&lt;h2 id="resources-from-the-presentation">Resources from the presentation&lt;/h2>
&lt;p>Examples on &lt;a href="https://github.com/aws-samples/amazon-aurora-call-to-amazon-sagemaker-sample" target="_blank" rel="noopener noreferrer">Github:&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener noreferrer">Amazon Aurora&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/rds/aurora/machine-learning/" target="_blank" rel="noopener noreferrer">Aurora machine learning&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/sagemaker" target="_blank" rel="noopener noreferrer">Amazon SageMaker&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>You can also &lt;a href="https://www.percona.com/resources/videos/anti-cheating-tool-massive-multiplayer-games-using-amazon-aurora-and-ml-services" target="_blank" rel="noopener noreferrer">watch a video of the recording&lt;/a>.&lt;/p></content:encoded><author>Cate Lawrence</author><category>Amazon</category><category>Amazon RDS</category><category>AWS</category><category>aws</category><category>ML</category><category>MySQL</category><category>mysql-and-variants</category><category>PostgreSQL</category><category>SQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/image4_hu_d89369f5a33f7ac7.jpg"/><media:content url="https://percona.community/blog/2020/06/image4_hu_f7cab3825f536fb9.jpg" medium="image"/></item><item><title>Percona Projects for Google Summer of Code - 2020</title><link>https://percona.community/blog/2020/06/04/percona-projects-for-google-summer-of-code-2020/</link><guid>https://percona.community/blog/2020/06/04/percona-projects-for-google-summer-of-code-2020/</guid><pubDate>Thu, 04 Jun 2020 11:38:43 UTC</pubDate><description> We are proud to announce that Percona was selected as a participating organization for the Google Summer of Code (GSoC) 2020 program, this is our second year as a participating org with the GSoC program.</description><content:encoded>&lt;p>
&lt;figure>&lt;img width="367" height="263" sizes="(max-width: 367px) 100vw, 367px" srcset="https://percona.community/blog/2020/06/google-summer-of-code-2019-367x263-1_hu_aa93dbc158b5eda0.webp 367w" src="https://percona.community/blog/2020/06/google-summer-of-code-2019-367x263-1_hu_aa93dbc158b5eda0.webp" alt="GSC" loading="lazy" />&lt;/figure>We are proud to announce that Percona was selected as a participating organization for the &lt;a href="https://summerofcode.withgoogle.com/" target="_blank" rel="noopener noreferrer">Google Summer of Code (GSoC) 2020 program&lt;/a>, this is our second year as a participating org with the GSoC program.&lt;/p>
&lt;p>GSoC is a great program to involve young student developers in open source projects. We participated in the program in 2019 for the first time and we were really happy and satisfied with the results. Percona Platform Engineering team decided to participate again for the 2020 program and we are glad and really happy to inform you that we were selected and welcome the student to work with our team during the summer of 2020 on their GSoC Project.&lt;/p>
&lt;h2 id="preparations">Preparations&lt;/h2>
&lt;p>We started planning for GSoC around November-December 2019, with the help from our Product Management team, we were able to shortlist a few ideas which we thought were really the right fit for our students, with Google Summer of Code, we realized it is very important to select projects which fit the &lt;a href="https://developers.google.com/open-source/gsoc/timeline?hl=en" target="_blank" rel="noopener noreferrer">timeline of the program&lt;/a> and justify the purpose of the project for both the student and organization, with the help of our Marketing and HR department were able to prepare a &lt;a href="https://www.percona.com/googlesummerofcode2020" target="_blank" rel="noopener noreferrer">landing page&lt;/a> for our potential GSoC Students with all relevant information about projects and communication platforms, from our past year’s experience and observation from other organizations,  we realized most of the students start their preparations right from the mid of January.&lt;/p>
&lt;p>Since this is just our second year as a participating organization we are really happy with the response we got from students, let’s look at the numbers and compare them with 2019, these numbers are based on org data exported from &lt;a href="https://summerofcode.withgoogle.com/" target="_blank" rel="noopener noreferrer">https://summerofcode.withgoogle.com/&lt;/a>
&lt;figure>&lt;img width="1816" height="1164" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_317f26367bd5c65a.webp 768w, https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_c5cadb6b1b225370.webp 480w, https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_5c9674c6cd1d6eeb.webp 1400w" src="https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_5c9674c6cd1d6eeb.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="student-and-projects">Student and Projects&lt;/h2>
&lt;p>The student intern who will be working with us is Meet Patel, This is the first time for Meet to be selected as a student intern with the GSoC Program.&lt;/p>
&lt;p>We selected two students for the program but unfortunately, one of our students failed to meet the eligibility criteria of the program and was dropped later.&lt;/p>
&lt;h3 id="meet-patel">Meet Patel&lt;/h3>
&lt;p>
&lt;figure>&lt;img width="765" height="1405" sizes="(max-width: 765px) 100vw, 765px" srcset="https://percona.community/blog/2020/06/Meet_Patel-1_hu_dbcb6ae4f70b331e.webp 480w, https://percona.community/blog/2020/06/Meet_Patel-1_hu_bdd49888b1ea1392.webp 765w" src="https://percona.community/blog/2020/06/Meet_Patel-1_hu_bdd49888b1ea1392.webp" alt="GSoC Student Meet Patel" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Meet is a 2nd year undergraduate at DAIICT, Gandhinagar, India; pursuing a bachelor’s degree in Information and Communication Technology with a minor in Computational Science. Meet is an open-source enthusiast and an avid developer, who is always excited to learn about new technologies.&lt;/p>
&lt;p>Meet will work on the GSoC project for the Refactoring of PMM Framework. PMM Framework is an automated testing framework that is used to set up PMM with various databases and their multiple instances, perform load tests and wipe everything after tests are done. One of the major objectives of this project is to make a well-documented script that helps easily set up PMM to the new users as well as refactoring it to make it more usable for internal testing.&lt;/p>
&lt;p>To track the progress of the project, please follow the &lt;a href="https://github.com/percona/pmm-qa/tree/GSOC-2020" target="_blank" rel="noopener noreferrer">GSoC Project Branch&lt;/a>. The Percona mentors for the project are Puneet Kala, Frontend/Web QA Automation Engineer, Nailya Kutlubaeva, QA Engineer The GSoC team at Percona is thankful to everyone involved in this year’s application and selection process. We are excited to have a team of mentors helping students learn about our products and working in open source. We’re looking forward to enjoying the two-way dialogue and guiding the students to hone their skills as they experience working on these valuable PMM developments.&lt;/p>
&lt;p>If you have any questions about GSoC Program please feel free to write to us on &lt;a href="mailto:gsoc@percona.com">gsoc@percona.com&lt;/a>&lt;/p></content:encoded><author>Puneet Kala</author><category>Community</category><category>Events</category><category>Google Summer of Code</category><category>GSoC</category><category>Information</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Percona Monitoring and Management</category><category>PMM</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/google-summer-of-code-2019-367x263-2_hu_691cafc8631da25c.jpg"/><media:content url="https://percona.community/blog/2020/06/google-summer-of-code-2019-367x263-2_hu_d6ee98ce0984dda6.jpg" medium="image"/></item><item><title>Percona Live ONLINE: MySQL on Google Cloud: War and Peace! by Akshay Suryawanshi &amp; Jeremy Cole</title><link>https://percona.community/blog/2020/06/02/percona-live-online-mysql-on-google-cloud-war-and-peace-by-akshay-suryawanshi-jeremy-cole/</link><guid>https://percona.community/blog/2020/06/02/percona-live-online-mysql-on-google-cloud-war-and-peace-by-akshay-suryawanshi-jeremy-cole/</guid><pubDate>Tue, 02 Jun 2020 16:12:51 UTC</pubDate><description>This session at Percona Live ONLINE was presented by Akshay Suryawanshi, Senior Production Engineer at Shopify, and Jeremy Cole, Senior Staff Production Engineer - Datastores at Shopify. Shopify is an online and on-premise commerce platform, founded in 2006.</description><content:encoded>&lt;p>This session at &lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live ONLINE&lt;/a> was presented by Akshay Suryawanshi, Senior Production Engineer at Shopify, and Jeremy Cole, Senior Staff Production Engineer - Datastores at Shopify. Shopify is an online and on-premise commerce platform, founded in 2006.&lt;/p>
&lt;p>Shopify is used by more than a million merchants, and hundreds of billions of dollars of sales have happened on the platform since its inception. The company is a large user of MySQL, and the Black Friday and Cyber Monday weekends are their peak dates during the year, handling hundreds of billions of queries with MySQL. This year’s presentation was an opportunity to talk about the company’s challenges and progress over the last twelve months.&lt;/p>
&lt;h2 id="key-google-cloud-concepts-from-the-presentation">Key Google Cloud concepts from the presentation&lt;/h2>
&lt;p>As part of the presentation, it’s important to understand the naming conventions that exist around Google Cloud:&lt;/p>
&lt;ul>
&lt;li>Regions - a geographic region where cloud operates (these could include a building or adjoining buildings)&lt;/li>
&lt;li>Zones - a subdivision inside particular regions. Typically there are three within each region, but it varies a bit by region.&lt;/li>
&lt;li>GCE - Google Compute Engine platform, the system provides virtual machines to run as servers (most of Shopify’s microscale infrastructure is on GCP and runs in VMs).&lt;/li>
&lt;li>Virtual machine instance - A GC virtual machine scheduled in a particular zone&lt;/li>
&lt;li>Persistent disk - A network-attached log-structured block storage zone&lt;/li>
&lt;li>GKE - Google’s Kubernetes Engine, a managed Kubernetes solution that is managed on top of Google Cloud Platform (GPC) and managed within Google Cloud.&lt;/li>
&lt;/ul>
&lt;h2 id="peacetime-stories">Peacetime stories&lt;/h2>
&lt;p>Akshay spoke about Persistent disks, which are Network-Attached, distributed log-structure, block storage: “This is the place where you basically say most of your data is, especially when you’re running MySQL data or any sort of databases.” Except for their performance, (which is usually affected by some degree of latency for network-attached storage) they provide incredible features, especially fast snapshotting of volumes.&lt;/p>
&lt;p>“We have utilized the snapshotting behavior to revamp our Backup and Restore infrastructure and brought down our recovery time to less than one hour for even a multi-terabyte disk. This is so incredibly fast that we actually restore each and every snapshot that we preserve or retain as a backup every single day. It’s happening in both regions where we run most of our MySQL fleet,” detailed Akshay.&lt;/p>
&lt;h2 id="configurable-vms">Configurable VMs&lt;/h2>
&lt;p>Virtual machines (VMs) expose an extensive API which is useful to do things programmatically with: “The API is very helpful. It is well documented, and we are using it in a bunch of places,” continued Akshay.&lt;/p>
&lt;p>Scaling VMs up and down are seamless operations (of course, most of them require a restart) and manageable. Provisioning new VMs in an appropriate region is very easy, according to Akshay: “Again because of the extensive API, which has provided something required to build resiliency against its own failures. So we spread our VMs across multiple zones. That helps us tremendously when a particular zone goes down. All of this has allowed us to build self-healing tooling to automatically replace failed VMs easily.”&lt;/p>
&lt;h2 id="gcp-is-truly-multi-regional">GCP is truly multi-regional&lt;/h2>
&lt;p>Google Cloud’s multi-region availability means failover from one region to another is easy and Shopify can move all its traffic from one region to another in just a few minutes, multiple times in a day. They can also expand to a distant geographical region without a lot of work, yet maintain the same stability.&lt;/p>
&lt;p>Akshay noted: “Isolating PII data has been a big win for Shopify in the past year when we launched a certain product where PII data needed to be preserved in a particular region, and GCP provides excellent support for that.”&lt;/p>
&lt;h2 id="google-kubernetes-engine">Google Kubernetes Engine&lt;/h2>
&lt;p>Kubernetes is an open-source project for container orchestration and Google Kubernetes Engine (GKE) is a feature-rich tool for using and running Kubernetes. According to Akshay: “Most of our future work is happening towards containers writing MySQL and running and scheduling them inside companies. The automatic storage and file system expansion are helpful in solving database problems.”&lt;/p>
&lt;p>Zone aware cluster node scheduling helps schedule the Kubernetes pods so that they are fault-tolerant towards zone failures.&lt;/p>
&lt;p>The GCP networking is simple to set up. Inter-regional latencies are pretty low, and Shopify can perform region failovers for databases quickly in the event of a disaster. “We can do a whole region, evac within a few minutes. This is because we can keep our databases in both regions up to date due to these low latencies,” explained Akshay.&lt;/p>
&lt;p>Virtual private clouds (VPCs) are a great way to segment the workloads. Isolating the networking connection at VPC level has helped this achievement.&lt;/p>
&lt;h2 id="war-some-of-the-things-that-can-go-wrong">War: Some of the things that can go wrong&lt;/h2>
&lt;p>Jeremy detailed some of the specific challenges that Shopify had faced over the last year, including stock outs which are when a resource requested (such as a VM or a disk) is not available at that time.&lt;/p>
&lt;p>Jeremy noted: “What that looks like is that you attempt to allocate it using some API, and it just takes a very long time to show up. In one particular instance, in one region, we had consistent PD and VM stockouts regularly occurring for several weeks.”&lt;/p>
&lt;p>It meant that the company had to adapt for when resources were not available at a moment’s notice, and to consider where time-critical components had to be resourced for availability.&lt;/p>
&lt;h2 id="trouble-in-persistent-disk-land">Trouble in persistent disk land&lt;/h2>
&lt;p>According to Jeremy: “One of the bigger problems that we’ve had in general is a persistent disk (PD).” An example was a recent outage caused by a change in persistent disks backend, which caused a regression “anywhere from minor latency impacts to full stalls for several seconds of the underlying PD volume, which of course, pretends to be a disk. So that means the disk is fully stalled for several seconds.”&lt;/p>
&lt;p>It took several weeks to diagnose and pin the blame of the stalls on PD properly. Jeremy noted, “The fun part of the story is that the mitigation for this particular problem involves attaching a substantial PD volume to every one of our VMs to work around a problem that was happening in PD. In order to do that, since we had so many VMs in aggregate, we had to allocate petabytes of persistent disk, and leave them attached for a few months.”&lt;/p>
&lt;p>Crucial to solving the problem was working closely with their vendor partner. As Jeremy explained, “Sometimes you have to get pretty creative to make things work right now and get yourself back in action.&lt;/p>
&lt;h2 id="troop-replacements">Troop replacements&lt;/h2>
&lt;p>Live migration (LM) was referred to in the previous year’s Shopify presentation at Percona Live, and the problem still persists according to Jeremy. “We continuously have machines being live migrated and their VMs being moved around between different physical machines.”&lt;/p>
&lt;p>The frequency of LM problems occurring and the number of times it causes this problem is directly related to the frequency of Linux kernel or Intel CDEs. “We’re still getting hostError instance failures where migrations fail and this kills the host,” explained Jeremy.&lt;/p>
&lt;p>Some live migrations are still breaking in NTP time sync. “And we are still periodically getting multiple migrations per VM for the same maintenance - up to 11 within a day or so.”&lt;/p>
&lt;h2 id="a-regional-ally-surrenders">A regional ally surrenders&lt;/h2>
&lt;p>In the last year, there was a regional outage: “Google had made a change to their traffic routing in one region, causing basically an overload of their networking stack. And we went down pretty hard because of that. There was nothing really that we could do about it,” said Jeremy. This was despite being deployed across multiple zones and multiple regions.&lt;/p>
&lt;p>Jeremy concluded the talk with a simple statement: Running MySQL in the cloud is not magic. “There are some unique challenges to Google Cloud, unique challenges to running MySQL in cloud infrastructure and unique challenges with the cloud itself. Sometimes running databases in the cloud can feel like you are constantly at war.”&lt;/p>
&lt;p>Preparing in advance as much as possible around how you manage your database in the cloud can help, particularly when you run at the kind of scale that Shopify does. However there will always be unexpected events and incidents. Working with your cloud partner and support providers can help here too.&lt;/p>
&lt;p>&lt;em>You can &lt;a href="https://www.percona.com/resources/videos/mysql-google-cloud-war-and-peace-akshay-suryawanshi-jeremy-cole-percona-live-online" target="_blank" rel="noopener noreferrer">watch a video of the recording&lt;/a> which includes a Q&amp;A at the end of the presentation.&lt;/em>&lt;/p></content:encoded><author>Cate Lawrence</author><category>DevOps</category><category>Google</category><category>Kubernetes</category><category>Kubernetes</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Shopify</category><media:thumbnail url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_1395b6e2186771a6.jpg"/><media:content url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_c0e4c47b55fa22a9.jpg" medium="image"/></item><item><title>Percona Live ONLINE Talk: Enhancing MySQL security at LinkedIn by Karthik Appigatla</title><link>https://percona.community/blog/2020/06/01/percona-live-online-talk-enhancing-mysql-security-at-linkedin-by-karthik-appigatla/</link><guid>https://percona.community/blog/2020/06/01/percona-live-online-talk-enhancing-mysql-security-at-linkedin-by-karthik-appigatla/</guid><pubDate>Mon, 01 Jun 2020 08:01:24 UTC</pubDate><description>MySQL, arguably the most popular relational database, is used pretty extensively at the popular professional social network LinkedIn. At Percona Live ONLINE 2020, the company’s flagship event held online for the first time due to the Covid-19 pandemic, Karthik Appigatla from LinkedIN’s database SRE team discussed the company’s approach to securing their database deployment without introducing operational hiccups or adversely affecting performance.</description><content:encoded>&lt;p>MySQL, arguably the most popular relational database, is used pretty extensively at the popular professional social network LinkedIn. At Percona Live ONLINE 2020, the company’s flagship event held online for the first time due to the Covid-19 pandemic, Karthik Appigatla from LinkedIN’s database SRE team discussed the company’s approach to securing their database deployment without introducing operational hiccups or adversely affecting performance.&lt;/p>
&lt;p>Instead of just performing admin duties, Karthik’s team builds automated tools to scale their infrastructure, and he talked about some of these tailored tools in his presentation. The database SREs on his team also work with the developers at LinkedIn and help them streamline their applications to make best use of the database.&lt;/p>
&lt;p>Talking about LinkedIn’s reliance on MySQL, Karthik said that not only do all their infrastructural tools rely on MySQL, many of the internal applications use MySQL as their backend datastore, and so do a few of the website facing applications as well.&lt;/p>
&lt;h2 id="database-proliferation">Database proliferation&lt;/h2>
&lt;p>The magnitude of the MySQL deployment at LinkedIn is pretty impressive. Thanks to the sheer number of microservices, each of which gets its own database, Karthik’s team looks after more than 2300 databases. These are powered by different versions of the MySQL server, namely v5.6, v5.7 and v8.0, all of which are hosted atop RHEL 7 installations.&lt;/p>
&lt;p>As he ran through the layout of the MySQL deployments at LinkedIn, Karthik mentioned that they have a multi-tenant architecture where multiple databases are hosted on a single MySQL server instance.&lt;/p>
&lt;p>MySQL is consumed as-a-service at LinkedIn and all the administrative tasks like backups, bootstrapping clusters, monitoring, and such are handled by automated systems built by Karthik’s team. He said that the level of automation is so high in fact that application owners can actually provision a database for their applications with just a few mouse clicks.&lt;/p>
&lt;h2 id="shared-responsibility">Shared responsibility&lt;/h2>
&lt;p>Given their scale of deployment, the developers at LinkedIn give special credence to the security of their databases. Karthik believes “security is a shared responsibility between the database SRE team and the application owners.”&lt;/p>
&lt;p>He illustrated how the databases are provisioned, from a security point of view and gave several security insights in his presentation based on his experience. For one, his team doesn’t take the easy approach of isolating databases by running multiple mysqld processes. This approach doesn’t scale well since the overhead on the server increases linearly as the number of databases it hosts increases.&lt;/p>
&lt;p>His description of how the various applications access different databases on the different servers was also pretty insightful for anyone looking to deploy databases at scale. One of the peculiar issues he described is that various components inside individual applications usually need to access different databases simultaneously. His team handled this by employing multiple user accounts with varying privileges.&lt;/p>
&lt;h2 id="access-control">Access control&lt;/h2>
&lt;p>He dwelled on this some more and spent some time explaining the different access management controls they’ve built into the system to facilitate access. One of the interesting security measures he talked about is how they limit the number of hosts that can access a database by adopting an IP-based grants system, which is slightly cumbersome to implement but a lot more secure.&lt;/p>
&lt;p>Also interesting is their approach to granting SSH access to the database servers to the SREs. Instead of the default public-key authentication, his team uses a certificate-based authentication scheme, and Karthik presented a high-level overview of this arrangement.&lt;/p>
&lt;p>Auditing and monitoring are also important aspects of security. At LinkedIn, the logins are audited by the &lt;a href="https://www.percona.com/doc/percona-server/LATEST/management/audit_log_plugin.html%E2%80%9D" target="_blank" rel="noopener noreferrer">Percona Audit Log plugin&lt;/a>, while the queries go through LinkedIN’s home-brewed Query Analyser agent. Karthik ran through the architecture of their Query Analyser agent, which LinkedIn plans to release under an open source license soon.&lt;/p>
&lt;p>Perhaps one of the biggest takeaways from the presentation was Karthik’s insight into the operational challenges that crop up due to their rather stringent security requirements, particularly their IP-based grants system. While the solutions he discussed were specific to LinkedIn, his presentation was peppered with tips and tricks that you can easily adapt for your deployments.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/resources/videos/enhancing-mysql-security-linkedin-karthik-appigatla-percona-live-online-2020" target="_blank" rel="noopener noreferrer">Click here to watch&lt;/a> Karthik’s presentation at Percona Live ONLINE 2020.&lt;/p></content:encoded><author>Mayank Sharma</author><category>Mayank Sharma</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>security</category><category>SRE</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_1395b6e2186771a6.jpg"/><media:content url="https://percona.community/blog/2020/06/SC-3-Matt-Percona_hu_c0e4c47b55fa22a9.jpg" medium="image"/></item><item><title>Join ProxySQL Tech Talks with Percona on June 4th, 2020!</title><link>https://percona.community/blog/2020/05/29/join-proxysql-tech-talks-with-percona-on-june-4th-2020/</link><guid>https://percona.community/blog/2020/05/29/join-proxysql-tech-talks-with-percona-on-june-4th-2020/</guid><pubDate>Fri, 29 May 2020 09:16:07 UTC</pubDate><description>Long months of the pandemic lockdown have brought to life many great online events enabling the MySQL community to get together and stay informed about the very recent developments and innovations available to MySQL users. It isn’t over yet! Next Thursday, June 4th, Percona &amp; ProxySQL are co-hosting the ProxySQL Tech Talks with Percona virtual meetup covering ProxySQL, MySQL and Percona XtraDB Cluster.</description><content:encoded>&lt;p>Long months of the pandemic lockdown have brought to life many great online events enabling the MySQL community to get together and stay informed about the very recent developments and innovations available to MySQL users. It isn’t over yet! Next &lt;strong>Thursday, June 4th&lt;/strong>, Percona &amp; ProxySQL are co-hosting the &lt;a href="https://bit.ly/2THdDqv" target="_blank" rel="noopener noreferrer">&lt;strong>ProxySQL Tech Talks with Percona&lt;/strong>&lt;/a> virtual meetup covering ProxySQL, MySQL and Percona XtraDB Cluster.&lt;/p>
&lt;p>The attendees are invited to participate in the &lt;a href="https://bit.ly/2THdDqv" target="_blank" rel="noopener noreferrer">two-hour deep-dive event&lt;/a> with plenty of time for questions and answers (we will have two 40-minute sessions + 20 minutes allocated for Q&amp;A). Get prepared, come with your burning questions and true war stories - we’ll have our speakers answer and comment on them! And here come the speakers:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>René Cannaò&lt;/strong>, ProxySQL author and CEO of ProxySQL Inc.&lt;/li>
&lt;li>&lt;strong>Vinicius M. Grippa&lt;/strong>, Senior Support Engineer at Percona.&lt;/li>
&lt;/ul>
&lt;p>René and Vinicius will give presentations covering the evolution of ProxySQL and ProxySQL’s native support for PXC 5.7 respectively:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ProxySQL, the journey from a MySQL proxy to being the de-facto multi-functional tool that scales MySQL&lt;/strong> by René Cannaò starts at &lt;strong>7 PM CEST&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>ProxySQL 2.0 native support for Percona XtraDB Cluster (PXC) 5.7&lt;/strong> by Vinicius Grippa starts at &lt;strong>8 PM CEST&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>The detailed abstracts, full agenda, and speaker bios are available on &lt;a href="https://bit.ly/2THdDqv" target="_blank" rel="noopener noreferrer">the event’s registration page&lt;/a>.&lt;/p>
&lt;p>The list of technologies &amp; tools covered at this event will include&lt;/p>
&lt;ul>
&lt;li>ProxySQL&lt;/li>
&lt;li>MySQL&lt;/li>
&lt;li>Percona XtraDB Cluster (PXC)&lt;/li>
&lt;li>Kubernetes (K8s)&lt;/li>
&lt;li> Percona Monitoring &amp; Management (PMM)&lt;/li>
&lt;li>AWS Aurora&lt;/li>
&lt;li>LDAP&lt;/li>
&lt;li>Galera Cluster&lt;/li>
&lt;/ul>
&lt;p>Our virtual room has already started to fill up, please register now to &lt;a href="https://bit.ly/2THdDqv" target="_blank" rel="noopener noreferrer">join us at ProxySQL Tech Talks with Percona&lt;/a> next Thursday at 7 PM CST! Hope to see many of you there!&lt;/p></content:encoded><author>Stacy Rostova</author><category>stacy</category><category>Containers</category><category>database</category><category>DBA Tools</category><category>Kubernetes</category><category>MySQL</category><category>MySQL</category><category>mysql-and-variants</category><category>Open Source Databases</category><category>Percona XtraDB Cluster</category><category>ProxySQL</category><category>PXC</category><category>tools</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/05/2160x1080-cover-Proxy-Percona-3_hu_b621f13821783f82.jpg"/><media:content url="https://percona.community/blog/2020/05/2160x1080-cover-Proxy-Percona-3_hu_4dde3d5c75fdbccc.jpg" medium="image"/></item><item><title>Anti-Cheating Tool for Massive Multiplayer Games Using Amazon Aurora and Amazon ML Services – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/13/anti-cheating-tool-for-massive-multiplayer-games-using-amazon-aurora-and-amazon-ml-services-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/13/anti-cheating-tool-for-massive-multiplayer-games-using-amazon-aurora-and-amazon-ml-services-percona-live-online-talk-preview/</guid><pubDate>Wed, 13 May 2020 16:06:06 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 4 p.m. • London 9 p.m. • New Delhi 1:30 a.m. (Wed) Level: Intermediate</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Tue 19 May • New York 4 p.m. • London 9 p.m. • New Delhi 1:30 a.m. (Wed)&lt;/em> &lt;em>Level:  Intermediate&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>Multiplayer video games are among the most lucrative online services. The overall games industry worldwide generated an estimated $174B in 2019, according to IDC. With this popularity, cheating becomes a common trend. Cheating in multiplayer games negatively impacts the game experience for players who play by the rules, and it becomes a revenue issue for game developers and publishers. According to Irdeto, 60% of online games were negatively impacted by cheaters, and 77% of players said they would stop playing a multiplayer game if they think opponents are cheating.&lt;/p>
&lt;p>Current methods for detecting and addressing cheating become difficult and expensive to operate as cheaters respond to the evolution of anti-cheating techniques. This session will show an effective method for game developers to continuously and dynamically improve their cheat-detection mechanisms. It uses Amazon Aurora and Amazon SageMaker for cheating detection, but can be adapted to other databases with similar capabilities. We’ll utilize the recently-launched Aurora machine learning functionality, which allows game developers to add ML-based predictions using the familiar SQL programming language without building custom integrations or learning separate tools. We’ll show which ML algorithms are useful for cheat detection and how an anti-cheat developer can write a single SQL query that handles the inputs and outputs for the algorithm.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>Machine learning is everywhere these days, or at least that’s how it feels when you work at Amazon. Some types of ML get a lot of attention, like self-driving cars, or services that take a JPEG and tell you if it’s a dog or a cat. But if you think about it, a vast amount of the world’s information is plain old tabular data in traditional relational databases. What about running ML on that data? Who knows what amazing insights and secrets are lurking inside?&lt;/p>
&lt;p>We’ll look at a cool video game example where we’re looking for cheaters, e.g. people who write bots to play on their behalf. We’ll show which ML models can detect these cheats and how to more easily run the analysis from your application, using tools that we’ve built. You should be able to run it on other databases if they have similar ML capabilities.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Application developers and database administrators who don’t know a whole lot about machine learning but would like to start.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>We’re curious what a complete online event will be like.  We’re looking forward to see how it compares to the traditional kind of conference.&lt;/p></content:encoded><author>Yoav Eilat</author><author>Yahav Biran</author><category>yoav.eilat</category><category>yahav.biran</category><category>AWS</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>State of the Dolphin – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/12/state-of-the-dolphin-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/12/state-of-the-dolphin-percona-live-online-talk-preview/</guid><pubDate>Tue, 12 May 2020 16:30:04 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 11:00 a.m. • London 4:00 p.m. • New Delhi 8:30 p.m.</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Tue 19 May • New York 11:00 a.m. • London 4:00 p.m. • New Delhi 8:30 p.m.&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>I will talk about the latest improvements in MySQL 8.0.20 and the MySQL Engineering Team’s steady progress with MySQL 8.0. These include solutions like Document Store, InnoDB Cluster, and InnoDB ReplicaSet where MySQL Router and MySQL Shell are playing an important role. All of these Oracle solutions are completely open source.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This talk is exciting because we will be looking at all the latest features in MySQL 8.0 Sadly my time will be probably too short to detail them all and cover the open source code contributions we’ve received from users.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>All MySQL users would benefit, whether newbies or veterans. You would be surprised how many people still have wrong assumptions about MySQL! So this talk is really for anyone seeking a fuller experience with MySQL, whether DBAs, developers, or others.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>I’m always very happy to learn new things about Vitess from Morgan Tocker and ProxySQL from René Cannaò.&lt;/p></content:encoded><author>Frédéric Descamps</author><category>frederic.descamps</category><category>Events</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>Kubernetes, The Swiss Army Knife For Your ProxySQL Deployments – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/08/kubernetes-the-swiss-army-knife-for-your-proxysql-deployments-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/08/kubernetes-the-swiss-army-knife-for-your-proxysql-deployments-percona-live-online-talk-preview/</guid><pubDate>Fri, 08 May 2020 02:02:48 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 10:00 p.m. • London 3:00 a.m. (Wed) • New Delhi 7:30 a.m. (Wed)</description><content:encoded>&lt;p>&lt;em>Percona Live Online Agenda Slot: Tue 19 May • New York 10:00 p.m. • London 3:00 a.m. (Wed) • New Delhi 7:30 a.m. (Wed)&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>ProxySQL is a high performance proxy from design to implementation. It speaks the MySQL protocol, and can go beyond load balancing. This talk covers various deployment options for ProxySQL in a Kubernetes environment.&lt;/p>
&lt;p>Typically ProxySQL is deployed in one of three ways depending on the scale and needs of your environment:&lt;/p>
&lt;ul>
&lt;li>Directly on each application server&lt;/li>
&lt;li>On a separate server (or layer)&lt;/li>
&lt;li>Cascaded, i.e. on each application server as well as a separate server (or layer)&lt;/li>
&lt;/ul>
&lt;p>This talk will cover how to successfully implement each of these ProxySQL deployment methods in Kubernetes using a highly scalable and robust approach.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>This was originally meant to be a tutorial, but it is now a talk, so it is not 3 hours cut into one, but tailored to whet your appetites for what is possible with ProxySQL on Kubernetes, which is an important topic in the community. I will share practical examples of deployment methods that have been implemented successfully in collaboration with large scale users.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>You should have an intermediate understanding of MySQL and how replication and proxying would work as well as at least a basic understanding of Kubernetes.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>All the talks on the &lt;a href="https://www.percona.com/live/percona-live-online-full-agenda" target="_blank" rel="noopener noreferrer">Percona Live agenda&lt;/a> are exciting, but if I had to pick one talk, it would be “Mostly Mistaken and Ignored Parameters While Optimizing a PostgreSQL Database” by Avi Vallarapu.&lt;/p></content:encoded><author>Raghavendra Prabhu</author><category>rene.cannao</category><category>Events</category><category>Kubernetes</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>MariaDB 10.4 and the Competition – Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/07/mariadb-10-4-and-the-competition-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/07/mariadb-10-4-and-the-competition-percona-live-online-talk-preview/</guid><pubDate>Thu, 07 May 2020 23:10:23 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 12:30 p.m. • London 5:30 p.m. • New Delhi 10:00 p.m.</description><content:encoded>&lt;p>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Tue 19 May • New York 12:30 p.m. • London 5:30 p.m. • New Delhi 10:00 p.m.&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>There are many good databases out there. Picking the right database for your project is never easy. There are technical criteria, business criteria, perhaps even ethical criteria. In this keynote, MariaDB Foundation CEO Kaj Arno will present his - obviously completely impartial - view of the process. Should you pick a database in the cloud or on premise? Should you pick an Open Source database or a closed-source one? And if you pick relational open source databases, how should you choose between MariaDB, MySQL and PostgresSQL? Expect the recommendation to not always be “go with MariaDB 10.4”. However, do expect to get a view of how the MariaDB Foundation sees its role, in relation to MariaDB Server, to MariaDB Corporation, to its other members (Microsoft, IBM, Service Now, Alibaba, Tencent, Booking.com, et al.), and above all, to the community of database developers and users.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>Can you expect a partial person to come with a neutral comparison between competitors? No. But it can still be logical and insightful. Can you expect such a comparison to be exciting? Yes. And it can be entertaining, too. Why? Because I am starting from the basic reasoning of “Cui bono”: Who benefits? From what? What is the likely reasoning by the actors in the database industry? And what is their actual behavior?&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Developers, DBAs, sysadmins. Anyone who needs to decide how to make data persistent in their apps. Where should data be stored? How should one even think about the choice process? Technology issues, business issues, the lot.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>On &lt;a href="https://www.percona.com/live/percona-live-online-full-agenda" target="_blank" rel="noopener noreferrer">the full agenda&lt;/a>, all the keynoters are great! The last few PeterZ presentations I’ve seen have been wonderful combinations of deep technical expertise and logical business reasoning. Matt Asay is always insightful. And there is a lot to be learned from Bruce Momjian and Frédéric Descamps.&lt;/p>
&lt;p>I’m also looking forward to MySQL on Google Cloud, by Leo Tolstoy and my former colleague Jeremy Cole. And speaking of former colleagues, Colin’s MariaDB Server talk is clearly going to be an exciting one, a different angle to what I will touch upon in my keynote.&lt;/p>
&lt;p>Last and by no means least: I already attended an earlier version of Valerii Kravchuk’s super-cool tracing and performance debugging presentation, but it was so good that I will want to look at it again.&lt;/p></content:encoded><author>Kaj Arnö</author><category>kaj.arno</category><category>Events</category><category>MariaDB</category><category>MySQL</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>Orchestrating Cassandra with Kubernetes Operator and Yelp PaaSTA - Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/06/orchestrating-cassandra-with-kubernetes-operator-and-yelp-paasta-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/06/orchestrating-cassandra-with-kubernetes-operator-and-yelp-paasta-percona-live-online-talk-preview/</guid><pubDate>Wed, 06 May 2020 19:42:03 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 3:00 p.m. • London 8:00 p.m. • New Delhi 12:30 a.m. (Wed) Level: Intermediate</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot: Tue 19 May • New York 3:00 p.m. • London 8:00 p.m. • New Delhi 12:30 a.m. (Wed)&lt;/em> &lt;em>Level: Intermediate&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>At Yelp, Cassandra, our NoSQL database of choice, has been deployed on AWS compute (EC2) and AutoScaling Groups (ASG), backed by Block Storage (EBS). This deployment model has been quite robust over the years while presenting its own set of challenges. To make our Cassandra deployment more resilient and reduce the engineering toil associated with our constantly growing infrastructure, we are abstracting Cassandra deployments further away from EC2 with Kubernetes and orchestrating with our Cassandra Operator. We are also leveraging Yelp’s PaaSTA for consistent abstractions and features such as fleet autoscaling with Clusterman, and Spot fleets, features that will be quite useful for an efficient datastore deployment.&lt;/p>
&lt;p>In this talk, we delve into the architecture of our Cassandra operator and the multi-region multi-AZ clusters it manages, and strategies we have in place for safe rollouts and zero-downtime migration. We will also discuss the challenges that we have faced en route and the design tradeoffs done. Last but not least, our plans for the future will also be shared.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>The talk not only delves into the architecture of Yelp’s Cassandra deployment on Kubernetes, and the operator but also the various challenges that we encountered and our approaches to them.  We also talk about how we have integrated this operator with our own PaaS (Platform-as-a-Service) called PaaSTA, and leveraged capabilities such as Spot fleets and Clusterman for significant savings in cloud costs.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>Attendees interested in stateful deployments - databases, streaming pipelines - on Kubernetes and orchestration systems in general, should find this talk interesting. Also, anyone using existing Kubernetes operators or planning on writing an operator should benefit from this talk.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>Among talks on &lt;a href="https://www.percona.com/live/percona-live-online-full-agenda" target="_blank" rel="noopener noreferrer">the full agenda,&lt;/a> I am looking forward to the State of Open Source Databases from Peter Zaitsev to get a snapshot of the current trends and technologies in the database world. Lefred’s talk on the State of the Dolphin should be similarly helpful in keeping up with the state of MySQL which is a rapidly growing project. Finally, given our current focus on databases and Kubernetes, I am also looking forward to Comparison of Kubernetes Operators for MySQL and A Step by Step Guide to Using Databases on Containers talks from Percona and AWS respectively.&lt;/p></content:encoded><author>Raghavendra Prabhu</author><category>raghu.prabhu</category><category>Events</category><category>Kubernetes</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>Dynamic Tracing for Finding and Solving MariaDB (and MySQL) Performance Problems on Linux - Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/05/dynamic-tracing-for-finding-and-solving-mariadb-and-mysql-performance-problems-on-linux-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/05/dynamic-tracing-for-finding-and-solving-mariadb-and-mysql-performance-problems-on-linux-percona-live-online-talk-preview/</guid><pubDate>Tue, 05 May 2020 21:13:57 UTC</pubDate><description>Percona Live Online Agenda Slot (CORRECTED): Wed 20 May • New York 6:00 a.m. • London 11:00 a.m. • New Delhi 3:30 p.m. Level: Advanced</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot (CORRECTED): Wed 20 May • New York 6:00 a.m. • London 11:00 a.m. • New Delhi 3:30 p.m.&lt;/em> &lt;em>Level: Advanced&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>While troubleshooting MariaDB server performance problems it is important to find out where the time is spent in the mysqld process, on-CPU and off-CPU. The process of investigation should have as small influence as possible on the server we try to troubleshoot.  Performance_schema introduced in MySQL 5.5 (and inherited from MySQL 5.6 by MariaDB) is supposed to provide detailed enough instrumentation for most cases. But it comes with a cost, requires careful sizing of performance counters, and the process of instrumenting the code is not yet complete even for MySQL 8, to say nothing about MariaDB with its 3rd party storage engines, plugins and libraries like Galera.&lt;/p>
&lt;p>This is when perf profiler and, on recent Linux kernels (4.9+) eBPF and bpftrace tools come handy.  Specifically, perf profiler and ftrace interface can be easily used while studying MariaDB performance problems. Basic usage steps are presented and several typical real life use cases (including adding dynamic probes to almost any line of MariaDB code) are discussed.  On Linux 4.9+ eBPF is probably the most powerful and least intrusive way to study performance problems. Basic usage of , bcc tools and bpftrace, as well as main bpftrace features and commands are demonstrated.  One of the ways to present and study stack samples collected by perf or bpftrace, Flame Graphs, is also presented with examples coming from my experince as a support engineer.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is your talk exciting?&lt;/h3>
&lt;p>It summarizes the experience from my recent years of practical non-trivial performance problems solving for MariaDB and MySQL systems in production. It turned out that application level instrumentation of database servers in MySQL ecosystem is not detailed and dynamic enough for some complex cases. We do not see Performance Schema instrumentation as every other line of MySQL code, yet, and even less so it applied to 3rd party plugins and technologies, like Galera.&lt;/p>
&lt;p>We can not expect developers to promptly add instrumentation where we need it and release custom binaries for every specific case, even in such a dynamic company like MariaDB Corporation, where we in services work closely with Engineering every day. That is why I personally got so excited when I found out that Linux starting for kernels 2.6.x (RHEL6) provides tools and approaches to add instrumentation almost anywhere, from kernel code to applications, dynamically, at run time, without any change needed in kernel or application code (something I’ve seen in action with DTrace on Solaris and OS X since 2008 or so).&lt;/p>
&lt;p>I started with perf profiler as a way to find out why some threads hanged for minutes when Performance Schema had not provided the answer, back in 2016, and this is when I first hit Brendan Gregg’s site (&lt;a href="http://www.brendangregg.com/" target="_blank" rel="noopener noreferrer">http://www.brendangregg.com/)&lt;/a>). Since that first real success with perf I follow him and dynamic tracing topic closely, and try to apply new tools added in the meantime while working on complex performance issues in MariaDB Support. I’ve shared my experience both in public and internally in MariaDB Corporation, and got several key MariaDB developers excited and happy about the details they can get from perf and dynamic tracing in general, comparing to any other approach. I’d like to convert more engineers to this faith with my presentation.&lt;/p>
&lt;p>I know about companies like Facebook having the entire teams working on custom dynamic tracing tools, and other MySQL Community members sharing their positive experience recently. Linux kernel developers work hard on making dynamic tracing even more safe, non-intrusive and easy to use. So, dynamic tracing (finally) becomes a hot topic that every database expert should follow!&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who would benefit the most from your talk?&lt;/h3>
&lt;p>I think experienced DBAs, as well as everyone working in professional services who cares about performance tuning on Linux would benefit a lot. But Linux sysadmins and application developers may get entirely new, different perspective on how to deal with performance problems when their application level instrumentation does not help to pinpoint the root cause. I consider dynamic tracing and profiling on modern Linux systems (starting from kernels 2.6.x, and especially 4.9+ with eBPF fully functional) a practice worth to be mastered by any IT professional these days.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What other presentations are you most looking forward to?&lt;/h3>
&lt;p>I am really interested in “Diagnosing Memory Utilization, Leaks and Stalls in Production” by Marcos Albe. I expect they my dear friend, former colleague and manager in Percona Support is exploring the same way of approaching performance problems (with Linux dynamic tracing tools) that I do. He probably started exploring this way earlier than me (my first attempts to use perf profiler while working on support issues date back only to 2016). He is also of the smartest people I ever worked with, and is working for a company that deals with complex performance problems on all kinds of forks of open source databases, not only MySQL, in all kinds of environments including containers. So I’ll surely benefit from his views and experience shared in this presentation. I hope to study more about eBPF-based dynamic tracing of memory allocations, cache and registers usage, memory flame graphs and similar tools applied in production to MySQL and other DBMSes.&lt;/p>
&lt;p>I am also looking forward to “Profiling MySQL and MariaDB Hash Join Implementations” by Jim Tommaney. MySQL Optimizer and query optimization in general are my area of interests since 2005 and I’d really want to find more details about the way hash joins are finally implemented, and comparison to various BKA-based optimizations we have for that in MariaDB. MySQL 8.0.x is a moving target now, with every minor release introducing new features, and I do not have enough time to keep my knowledge current on this topic. That’s why i expect both a useful review and summary, and details about changes introduced by recent MySQL 8.0.20 in this area.&lt;/p>
&lt;p>Overall &lt;a href="https://www.percona.com/live/percona-live-online-full-agenda" target="_blank" rel="noopener noreferrer">the conference agenda&lt;/a> looks really great, and i am considering taking a full day (if not two) off to spend most of these 24 hours online listening to talks.&lt;/p></content:encoded><author>Valeriy Kravchuk</author><category>valeriy.kravchuk</category><category>Events</category><category>MariaDB</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>Expert MariaDB: Utilize MariaDB Server Effectively - Percona Live ONLINE Talk Preview</title><link>https://percona.community/blog/2020/05/04/expert-mariadb-utilize-mariadb-server-effectively-percona-live-online-talk-preview/</link><guid>https://percona.community/blog/2020/05/04/expert-mariadb-utilize-mariadb-server-effectively-percona-live-online-talk-preview/</guid><pubDate>Mon, 04 May 2020 20:30:20 UTC</pubDate><description>Percona Live Online Agenda Slot: Tue 19 May • New York 11:00 p.m. • London 4:00 a.m. (Wed) • New Delhi 8:30 a.m. (Wed) Level: Intermediate</description><content:encoded>&lt;p>&lt;em>&lt;a href="https://www.percona.com/live/conferences" target="_blank" rel="noopener noreferrer">Percona Live Online&lt;/a> Agenda Slot:  Tue 19 May • New York&lt;/em> &lt;em>11:00 p.m. • London 4:00 a.m. (Wed) • New Delhi 8:30 a.m. (Wed)&lt;/em> &lt;em>Level: Intermediate&lt;/em>&lt;/p>
&lt;h3 id="abstract">Abstract&lt;/h3>
&lt;p>MariaDB Server 10.4 has been out for some time now (June 2019) and it has many new features, some of which MySQL does not have. Feature-wise, it is important to know what MariaDB Server 10.4 has (e.g. system tables in the Aria storage engine, ability to reload SSL certificates without a restart and more!) and what it lacks compared to MySQL 8.0 (group replication, the X Protocol, etc.)  Attendees will become more knowledgeable about how to better manage, observe, and secure their MariaDB Servers.&lt;/p>
&lt;h3 id="why-is-your-talk-exciting">Why is Your Talk Exciting?&lt;/h3>
&lt;p>I am going to talk about MariaDB Server from a user perspective and why you might consider using this fork of MySQL for your production use cases. After all, it has progressed differently from MySQL and has features that are similar, sometimes implemented differently, yet it also has new features that MySQL may not get to, e.g. Oracle compatibility.&lt;/p>
&lt;p>It is also likely that we can talk a little about MariaDB Server 10.5 which should be just around the corner, as it is currently in beta. There are plenty of improvements around JSON, more information reported in the threadpool, a new Amazon S3 storage engine, plenty of InnoDB improvements, Galera 4 inconsistency voting, and more.&lt;/p>
&lt;h3 id="who-would-benefit-the-most-from-your-talk">Who Would Benefit the Most From Your Talk?&lt;/h3>
&lt;p>Are you MariaDB curious? You would enjoy this talk as it is will only cover features not already present in MySQL. After all, it doesn’t matter how things are implemented — this is totally from a user perspective, so if you’re already used to MySQL, find out what *else* you will get from MariaDB Server.&lt;/p>
&lt;h3 id="what-other-presentations-are-you-most-looking-forward-to">What Other Presentations Are You Most Looking Forward To?&lt;/h3>
&lt;p>I’m interested in the ProxySQL talks, though &lt;a href="https://www.percona.com/live/percona-live-online-full-agenda" target="_blank" rel="noopener noreferrer">the entire agenda&lt;/a> is great.&lt;/p></content:encoded><author>Colin Charles</author><category>Colin.Charles</category><category>Events</category><category>MariaDB</category><media:thumbnail url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_5f15f9d3f957c60b.jpg"/><media:content url="https://percona.community/blog/2020/05/Social-PL-Online-2020-1_hu_6e248db3cecf756e.jpg" medium="image"/></item><item><title>How to contribute Dashboards to PMM</title><link>https://percona.community/blog/2020/05/04/how-to-contribute-dashboards-to-pmm/</link><guid>https://percona.community/blog/2020/05/04/how-to-contribute-dashboards-to-pmm/</guid><pubDate>Mon, 04 May 2020 14:54:56 UTC</pubDate><description>Have you already contributed to Percona’s open-source products or perhaps you wanted to try doing so?</description><content:encoded>&lt;p>Have you already contributed to Percona’s open-source products or perhaps you wanted to try doing so?&lt;/p>
&lt;p>I will tell you how to become a contributor to the popular open-source product from Percona in just a few hours. You don’t need any serious developer skills.&lt;/p>
&lt;p>We earlier explained how to contribute to PMM documentation in &lt;a href="https://www.percona.com/community-blog/2020/01/28/how-to-contribute-to-pmm-documentation/" target="_blank" rel="noopener noreferrer">our last post&lt;/a>. Now we will contribute to PMM itself, namely to Dashboards. Dashboards are an important part of PMM, they are seen and used by thousands of users, so your contribution may be of benefit to many others.&lt;/p>
&lt;p>You can view the latest version of our demo at &lt;a href="https://pmmdemo.percona.com/graph/" target="_blank" rel="noopener noreferrer">https://pmmdemo.percona.com/graph/&lt;/a>.&lt;/p>
&lt;p>The purpose of this latest article is to introduce you to the process of making changes to Dashboards in PMM, such as creating a new dashboard or improving an existing one. If you want to become a contributor, you will need to repeat the steps from &lt;a href="https://www.percona.com/community-blog/2020/01/28/how-to-contribute-to-pmm-documentation/" target="_blank" rel="noopener noreferrer">my earlier post&lt;/a>.&lt;/p>
&lt;p>You need to:&lt;/p>
&lt;ol>
&lt;li>Have PMM installed on your server. PMM is easy to install via Docker.&lt;/li>
&lt;li>Have a GitHub account and install Git on your computer.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="2328" height="1254" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_b7242c0b428ee426.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_57bce36214d2b865.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_eec5a8b9048a3399.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_eec5a8b9048a3399.webp" alt="How to contribute Dashboards to PMM" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="what-kind-of-contribution-should-i-make">What kind of contribution should I make?&lt;/h2>
&lt;p>Of course, this is the first question to decide. PMM is a great product that many developers are working on. PMM uses &lt;a href="https://perconadev.atlassian.net/projects/PMM/issues/PMM-4923?filter=allopenissues" target="_blank" rel="noopener noreferrer">JIRA&lt;/a> to track development tasks. You can:&lt;/p>
&lt;ol>
&lt;li>Explore the tasks and choose an interesting one&lt;/li>
&lt;li>Create your own task from scratch&lt;/li>
&lt;/ol>
&lt;p>When I used PMM, I noticed that many charts have useful tooltips.  Although you can make any sort of contribution, in this article I will use the simplest type of contribution, a tooltip.&lt;/p>
&lt;p>Here’s the value of tooltips:&lt;/p>
&lt;blockquote>
&lt;p>Tooltips - they are written by experts, for consumption by non-experts.  One of Percona’s value-add is to write good tooltips that are useful. We (Perconians) know the technologies and we have people who are used to simplifying complex topics.&lt;/p>&lt;/blockquote>
&lt;p>
&lt;figure>&lt;img width="1958" height="1508" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_73e531e907bb7fa3.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_9d052fa79a85d77f.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_14492fea54213415.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_14492fea54213415.webp" alt="Tooltips" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>There are a lot of widgets that haven’t been described yet, so tooltips would hugely increase user experience here. You can open the widget settings and do the following:&lt;/p>
&lt;ol>
&lt;li>See settings, functions and parameters on which the chart is built.&lt;/li>
&lt;li>Study the documentation for these parameters&lt;/li>
&lt;li>Write a tooltip.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>&lt;img width="1918" height="740" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_cfafadf75764f44a.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_1b9d079cd9ca0d40.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_c1a7a6505b3532f2.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_c1a7a6505b3532f2.webp" alt="PMM Dashboard Settings" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now that we have defined what we’re about to do, let’s make a tooltip for one of the charts.&lt;/p>
&lt;p>I opened JIRA and created a task where I described what I would do:&lt;/p>
&lt;p>&lt;strong>Tooltips: Prometheus dashboards: Head Block: Update graph panel description&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://perconadev.atlassian.net/browse/PMM-5053" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PMM-5053&lt;/a>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2232" height="1410" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_4577ca8c9c8544f0.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_8e03f776d1ba1288.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_ef297e715c75ac92.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_ef297e715c75ac92.webp" alt="PMM Dashboards Jira Issue" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="well-find-a-repository-for-the-dashboards">We’ll find a repository for the Dashboards&lt;/h2>
&lt;p>We’ll make changes to the code.&lt;/p>
&lt;p>PMM is big, for convenience it has a lot of GitHub repositories which can be found in the main repository &lt;a href="https://github.com/percona/pmm/tree/PMM-2.0" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm/tree/PMM-2.0&lt;/a>.&lt;/p>
&lt;p>Since I will be contributing to Dashboards, I will need a Grafana Dashboard repository: &lt;a href="https://github.com/percona/grafana-dashboards" target="_blank" rel="noopener noreferrer">https://github.com/percona/grafana-dashboards&lt;/a>&lt;/p>
&lt;p>Next I make a fork of this repository in my GitHub account. A fork is needed to check my changes before sending them to the main repository.&lt;/p>
&lt;p>By the way, more than 600 people have already done it. You can do it, too! :)
&lt;figure>&lt;img width="2100" height="852" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_16045cfe2746e7de.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_f1229cff3fb98eb6.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_32c2c9bed081ad7.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_32c2c9bed081ad7.webp" alt="PHH Dashboards Contribution GitHub " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="lets-study-the-structure-of-the-dashboard">Let’s study the structure of the Dashboard&lt;/h2>
&lt;p>All Dashboards are located in the “dashboards” folder and each dashboard is a JSON file.&lt;/p>
&lt;p>An example can be found here: &lt;a href="https://github.com/percona/grafana-dashboards/tree/master/dashboards" target="_blank" rel="noopener noreferrer">https://github.com/percona/grafana-dashboards/tree/master/dashboards&lt;/a>&lt;/p>
&lt;p>Next I have to:&lt;/p>
&lt;ol>
&lt;li>Find the JSON file I need&lt;/li>
&lt;li>Understand what needs to be changed&lt;/li>
&lt;li>Change it&lt;/li>
&lt;li>Commit and send a Pull Request for review&lt;/li>
&lt;li>Celebrate&lt;/li>
&lt;/ol>
&lt;p>It is important that all contributions are carefully reviewed. When I wrote this article, I changed only a few lines, but even this was in review for several days by different expert advisors.&lt;/p>
&lt;h2 id="changing-the-dashboard-is-easy">Changing the Dashboard is easy&lt;/h2>
&lt;p>I don’t have to know JSON. You can change Dashboards directly in the PMM interface. All settings are saved in JSON. Each chart has a button “Panel JSON”, which allows you to display JSON code.
&lt;figure>&lt;img width="1076" height="632" sizes="(max-width: 1076px) 100vw, 1076px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_6_hu_c89666ba815d039d.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_6_hu_4ba4c3696302f7a0.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_6_hu_e7d3602de78a2aee.webp 1076w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_6_hu_e7d3602de78a2aee.webp" alt="Changing the Dashboard is easy" loading="lazy" />&lt;/figure> That way, I can:&lt;/p>
&lt;ol>
&lt;li>View the chart settings&lt;/li>
&lt;li>Make the necessary changes&lt;/li>
&lt;li>Save and get the necessary JSON file&lt;/li>
&lt;/ol>
&lt;p>If you look at the chart settings, you can understand what functions and arguments they use and check out the documentation:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/" target="_blank" rel="noopener noreferrer">https://prometheus.io/docs/prometheus/latest/querying/functions/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://prometheus.io/docs/prometheus/latest/querying/operators/" target="_blank" rel="noopener noreferrer">https://prometheus.io/docs/prometheus/latest/querying/operators/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Review the documentation to make the correct description for the chart or make other improvements to the chart.&lt;/p>
&lt;p>As a next step, I need to add value to the Description field. As soon as I add it, I immediately get the tooltip for the chart.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1486" height="1340" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_ebedd0c47071f57f.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_ed5ef0c9d133af7a.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_c1b06cc7103a71e7.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_c1b06cc7103a71e7.webp" alt=" Description field" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="save-the-result">Save the result&lt;/h2>
&lt;p>I add the Description and save the chart. Then I open the JSON widget and find my value in the “description” field. It’s simple. I need to move the changes to the git repository.&lt;/p>
&lt;p>If I had created a new Dashboard or a chart, it would have been easier for me to transfer the entire file to the repository. But since I only have one line changed, I will only move it.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1546" height="1008" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_b302a405aa0bab1b.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_cd3e03ccbbf8f3a0.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_a009c87ec8cfaed.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_a009c87ec8cfaed.webp" alt="JSON" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>I made my fork repository’s git clone to a computer beforehand.&lt;/p>
&lt;ol>
&lt;li>I open the dashboards/Prometheus.json file&lt;/li>
&lt;li>I find the “title” block: “Head Block”&lt;/li>
&lt;li>I add a line with “description” and save the file&lt;/li>
&lt;/ol>
&lt;h2 id="working-with-the-pmm-repository">Working with the PMM repository&lt;/h2>
&lt;p>I have already described in detail the work with the repository in the previous article (link), and you can also use the instructions in the repository itself:&lt;/p>
&lt;p>&lt;a href="https://github.com/percona/grafana-dashboards/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">https://github.com/percona/grafana-dashboards/blob/master/CONTRIBUTING.md&lt;/a>&lt;/p>
&lt;p>I created a separate branch, named the commit correctly and sent it to my repository.&lt;/p>
&lt;p>I then made a Pull Request to the main grafana-dashboards repository.&lt;/p>
&lt;p>I really liked the process of testing the repository. I’ll tell you the steps.&lt;/p>
&lt;h3 id="contributor-license-agreement">Contributor License Agreement&lt;/h3>
&lt;p>The first step is to sign the license/cla Contributor License Agreement. This is done with your GitHub account and one button. Simply read and agree.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2146" height="1378" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_3d58c91eaf122cc.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_f2a05e9ffd95376e.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_eec9aefd83cd2572.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_eec9aefd83cd2572.webp" alt="Contributor License Agreement" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="automated-code-review">Automated code review&lt;/h3>
&lt;p>Your branch will pass an automated code check using the &lt;a href="https://codecov.io/" target="_blank" rel="noopener noreferrer">Codecov&lt;/a> service.&lt;/p>
&lt;p>You will be able to see the process and you will see the result: Codacy/PR Quality Review Up to standards.&lt;/p>
&lt;p>A positive pull request.
&lt;figure>&lt;img width="1562" height="1160" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Codecov_hu_27953fda143edfdc.webp 768w, https://percona.community/blog/2020/05/Codecov_hu_dc4555ca3fdc0304.webp 480w, https://percona.community/blog/2020/05/Codecov_hu_5e85b77021aeb596.webp 1400w" src="https://percona.community/blog/2020/05/Codecov_hu_5e85b77021aeb596.webp" alt="Codecov" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="continuous-integration-ci">Continuous integration (CI)&lt;/h3>
&lt;p>After each commit, Jenkins CI will try to build a PMM to:&lt;/p>
&lt;ol>
&lt;li>Make sure that your changes do not break the PMM&lt;/li>
&lt;li>Run auto testing&lt;/li>
&lt;/ol>
&lt;p>It takes a few minutes.&lt;/p>
&lt;p>I’m sure you’ll pass all the automatic checks.&lt;/p>
&lt;p>You can try to start the build yourself using the instructions in the repository.&lt;/p>
&lt;p>If you are interested in these processes, please let me know in the comments.
&lt;figure>&lt;img width="1658" height="1282" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Jenkins-1_hu_3312d59d4f7fcd49.webp 768w, https://percona.community/blog/2020/05/Jenkins-1_hu_f59a95e2504c9c9f.webp 480w, https://percona.community/blog/2020/05/Jenkins-1_hu_20e1def7a745b677.webp 1400w" src="https://percona.community/blog/2020/05/Jenkins-1_hu_20e1def7a745b677.webp" alt="Jenkins" loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="expert-review-and-code-review">Expert review and code review&lt;/h2>
&lt;p>Percona experts check all code changes. The more changes, the more experts will be involved.&lt;/p>
&lt;p>While I was writing this article, I made several contributions to Dashboards PMM.&lt;/p>
&lt;ol>
&lt;li>When I changed one line to add a tooltip, my code was reviewed by 2 people: the person responsible for Dashboards and those leaders.&lt;/li>
&lt;li>When I added a 50 line instruction, it already needed to be reviewed by 4 people.&lt;/li>
&lt;/ol>
&lt;p>After each task is completed in JIRA, they will be checked by the QA department.&lt;/p>
&lt;p>You should not worry about the review process. Percona experts are very friendly, they will write recommendations directly to GitHub. They can even correct some lines at once.&lt;/p>
&lt;p>If you have any questions, just text me, I’ll try to help.&lt;/p>
&lt;p>I received a few recommendations, made some changes and my contribution was accepted.
&lt;figure>&lt;img width="2172" height="1378" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_12b0a9b3b984d27.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_714037d0a394125c.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_404b61669a45fa87.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_404b61669a45fa87.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>I became a PMM contributor by improving one of the Dashboards. I spent about 30-60 minutes a day and it took me less than a week.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1514" height="1468" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_3aa6056b2156ad3e.webp 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_bfb2e8d9409e9df2.webp 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_8a94eeb1a42ec800.webp 1400w" src="https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_8a94eeb1a42ec800.webp" alt="Result" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In the process, I was able to add instructions for future contributors (link). You can improve this manual, too.&lt;/p>
&lt;p>I urge you to become a contributor. If you need help, just email me.&lt;/p>
&lt;p>More ideas for contributions can be found here: &lt;a href="https://www.percona.com/community/contributions/pmm" target="_blank" rel="noopener noreferrer">Link&lt;/a>&lt;/p>
&lt;h2 id="my-references">My references&lt;/h2>
&lt;p>Home page of the PMM contributor: &lt;a href="https://www.percona.com/community/contributions/pmm" target="_blank" rel="noopener noreferrer">https://www.percona.com/community/contributions/pmm&lt;/a>&lt;/p>
&lt;p>An article on how to become a documentation contributor:&lt;/p>
&lt;ul>
&lt;li>Instructions for Contributors: Issue at JIRA: &lt;a href="https://perconadev.atlassian.net/browse/PMM-5053" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PMM-5053&lt;/a>&lt;/li>
&lt;li>A branch in my repository: &lt;a href="https://github.com/dbazhenov/grafana-dashboards/tree/PMM-5053_dbazhenov_tooltip" target="_blank" rel="noopener noreferrer">https://github.com/dbazhenov/grafana-dashboards/tree/PMM-5053_dbazhenov_tooltip&lt;/a>&lt;/li>
&lt;li>Pull Request to PMM repository &lt;a href="https://github.com/percona/grafana-dashboards/pull/524" target="_blank" rel="noopener noreferrer">https://github.com/percona/grafana-dashboards/pull/524&lt;/a>&lt;/li>
&lt;li>Confirmed by CLA: &lt;a href="https://cla-assistant.percona.com/percona/grafana-dashboards?pullRequest=524" target="_blank" rel="noopener noreferrer">https://cla-assistant.percona.com/percona/grafana-dashboards?pullRequest=524&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Daniil Bazhenov</author><category>daniil.bazhenov</category><category>Entry Level</category><category>Information</category><category>Intermediate Level</category><category>Open Source Databases</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_339f1f46980870f7.jpg"/><media:content url="https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_fca739b2b4e871b7.jpg" medium="image"/></item><item><title>The Anatomy of LuaJIT Tables and What's Special About Them</title><link>https://percona.community/blog/2020/04/29/the-anatomy-of-luajit-tables-and-whats-special-about-them/</link><guid>https://percona.community/blog/2020/04/29/the-anatomy-of-luajit-tables-and-whats-special-about-them/</guid><pubDate>Wed, 29 Apr 2020 13:18:37 UTC</pubDate><description>I don’t know about you, but I really like to get inside all sorts of systems. In this article, I’m going to tell you about the internals of Lua tables and special considerations for their use. Lua is my primary professional programming language, and if one wants to write good code, one needs at least to peek behind the curtain. If you are curious, follow me.</description><content:encoded>&lt;p>I don’t know about you, but I really like to get inside all sorts of systems. In this article, I’m going to tell you about the internals of Lua tables and special considerations for their use. Lua is my primary professional programming language, and if one wants to write good code, one needs at least to peek behind the curtain. If you are curious, follow me.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1200" height="630" sizes="(max-width: 1200px) 100vw, 1200px" srcset="https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_f6a8421a996fede4.webp 768w, https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_67ee0325940b46a7.webp 480w, https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_bc0f643532858e85.webp 1200w" src="https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_bc0f643532858e85.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Lua has several implementations and several versions. In this article, I’m going to discuss mostly LuaJIT 2.1.0, which is used in Tarantool. Our version is a bit patched as compared to the authentic LuaJIT, but the differences don’t impact tables.&lt;/p>
&lt;p>There is another good presentation about tables in &lt;a href="https://yszheda.github.io/lua-table-talk/" target="_blank" rel="noopener noreferrer">PUC-Rio implementation&lt;/a>, if you’re interested. You can also find this on &lt;a href="https://www.slideshare.net/ShuaiYuan/the-basics-and-design-of-lua-table" target="_blank" rel="noopener noreferrer">slideshare.net.&lt;/a>&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Tables in Lua are the only composite data type designed to suit any purpose. A table can be used both as a data array (if the keys are integer-valued) and as a key-value repository. Values of any type can be used as keys (except for nil). Scientifically speaking, a table is an &lt;a href="https://www.lua.org/pil/2.5.html" target="_blank" rel="noopener noreferrer">associative array&lt;/a>.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">-- Empty table
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local t1 = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- Table as an array
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local t2 = { 'Sunday', 'Monday', 'Im tired' }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- Table as a hashtable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local t3 = {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cat = 'meow',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dog = 'woof',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cow = 'moo',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- Ordered map
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">local t4 = {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 'k1', 'k2', 'k3' -- stored in the array part
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['k1'] = 'v1', -- stored in the hash part
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['k2'] = 'v2', -- stored in the hash part
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ['k3'] = 'v3', -- stored in the hash part
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="anatomy-of-tables">Anatomy of tables&lt;/h2>
&lt;p>In &lt;a href="https://github.com/LuaJIT/LuaJIT/blob/v2.1/src/lj_obj.h#L482-L495" target="_blank" rel="noopener noreferrer">LuaJIT sources&lt;/a>, a table is represented by the following structure (I’ve omitted a part of fields which are not relevant here for simplicity):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">typedef struct GCtab {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /* GC stuff */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MRef array; /* Array part. */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MRef node; /* Hash part. */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uint32_t asize; /* Size of array part (keys [0, asize-1]). */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uint32_t hmask; /* Hash part mask (size of hash part - 1). */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">} GCtab;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A table has two parts: array and hashmap. Both are represented by continuous storage areas. I was about to describe the logic LuaJIT is guided by when new elements are inserted, but this a rather sophisticated algorithm. Moreover, as a Lua developer, I have no need to speculate about what a table looks like on the inside. There’s something that is more important: two tables may contain the same keys and values, but differ in terms of internal representation. This internal representation will affect behaviors of some functions, which I’m going to discuss here.&lt;/p>
&lt;p>For demonstration, I took LuaJIT source code and patched the tostring() method a bit so that it would print out the size and all the contents of C structure to stdout using printf().&lt;/p>
&lt;h3 id="what-the-patch-is-about">What the patch is about&lt;/h3>
&lt;p>The most complete version of code from the publication, including all the experiments, can be found on &lt;a href="https://github.com/rosik/luajit/compare/tarantool...rosik:habr-luajit-tables" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>. Conceptually, this is what the code looks like:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">diff --git a/src/lj_strfmt.c b/src/lj_strfmt.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">index d7893ce..45df53c 100644
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- a/src/lj_strfmt.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+++ b/src/lj_strfmt.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@@ -392,6 +392,51 @@ GCstr * LJ_FASTCALL lj_strfmt_obj(lua_State *L, cTValue *o)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (tvisfunc(o) &amp;&amp; isffunc(funcV(o))) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> p = lj_buf_wmem(p, "builtin#", 8);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> p = lj_strfmt_wint(p, funcV(o)->c.ffid);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ } else if (tvistab(o)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ GCtab *t = tabV(o);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ /* print array part */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ printf("-- a[%d]: ", asize);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ for (i = 0; i &lt; asize; i++) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ // printf(...);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ /* print hashmap part */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ printf("-- h[%d]: ", hmask+1);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ for (i = 0; i &lt;= hmask; i++) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ // printf(...);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> p = lj_strfmt_wptr(p, lj_obj_ptr(o));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let me show how this works:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x40eae3a8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[1]: nil=nil&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’ve created an empty table, and LuaJIT has allocated storage space for 0 array elements and 1 element in hashmap, to which is placed nil key and nil value (i.e. nothing). Now let’s try to populate this table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t["a"] = "A"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">t["b"] = "B"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">t["c"] = "C"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x40eae3a8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[4]: b=B, nil=nil, a=A, c=C&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>String keys are added to hashmap as expected. Here the first reason becomes visible as to why the internal representation of tables may differ: hash collisions. To resolve hash collisions, LuaJIT uses a hybrid of open addressing and &lt;a href="https://github.com/LuaJIT/LuaJIT/issues/494#issuecomment-487373965" target="_blank" rel="noopener noreferrer">separate chaining methods&lt;/a> (thanks to &lt;a href="https://imun.cloud/" target="_blank" rel="noopener noreferrer">Igor Munkin&lt;/a> for clarification).&lt;/p>
&lt;p>Depending on the order in which elements are added to the table, their order during integration will differ. For demonstration, I’ve set up one more auxiliary function traverse(), which runs for cycle through the table and prints out its contents.&lt;/p>
&lt;h3 id="implementation-of-traverse">Implementation of traverse&lt;/h3>
&lt;p>A complete version of the code can still be found on &lt;a href="https://github.com/rosik/luajit/compare/tarantool...rosik:habr-luajit-tables" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>. For brevity, I’m going to demonstrate only its operating principle here.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function traverse(fn, t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local str = ''
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for k, v, n in fn(t) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> str = str .. string.format('%s=%s ', k, v)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> print(str)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t1 = {a = 1, b = 2, c = 3}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x40eaeb08
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[4]: b=2, nil=nil, a=1, c=3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">t2 = {c = 3, b = 2, a = 1}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x40ea7e70
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[4]: b=2, nil=nil, c=3, a=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(pairs, t1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- b=2, a=1, c=3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(pairs, t2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- b=2, c=3, a=1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>One more interesting fact about the internals of tables: the deletion of a value does not lead to deletion of a key. Sounds like Captain Obvious, but it’s true.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t2["c"] = nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(pairs, t2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- b=2, a=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x411c83c0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[4]: b=2, nil=nil, c=nil, a=1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(next(t2, "c"))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is done for a reason, to make it possible to delete values and not disturb iteration order. What is strongly discouraged is to add new keys to the table within the cycle as reallocation or re-hashing may occur, and then iteration will bust.&lt;/p>
&lt;p>&lt;em>An additional note&lt;/em> from &lt;a href="https://imun.cloud/" target="_blank" rel="noopener noreferrer">Igor Munkin&lt;/a>: the reason is lookup in general, not iteration. Collisions are resolved by the chain method. Generally, when the main node is deleted for the searched key, the link to the colliding element needs to be reassigned to the predecessor (if present) of this main node. Task O(n) to search for it needs to be performed when deleting. The cost of a search does not worsen when a dead node is present on the way to collision resolution. See also &lt;a href="https://github.com/LuaJIT/LuaJIT/issues/494#issuecomment-487373965" target="_blank" rel="noopener noreferrer">this reference.&lt;/a>&lt;/p>
&lt;h3 id="arrays">Arrays&lt;/h3>
&lt;p>OK, iteration for string keys is clear, no more questions. Now let’s talk about the second half of the table — the array.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = {1, 2}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x41735918
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[3]: nil, 1, 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[1]: nil=nil&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lua is often made fun of because indexation in this language starts with one. Interestingly, LuaJIT allocates space to store the null element anyway. This makes it easier for it to avoid multiple addition / deduction of one. If it appears to you that arrays don’t have any surprises, then I’m here to disappoint you (or to make you happy) — they do:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = {[2] = 2, 1}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x416a3998
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[2]: nil, 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[2]: nil=nil, 2=2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I’ve changed syntax a bit, so one element gets to the array, and another gets to the hashmap. So consider yourself warned: it’s no use taking guesses about internal representation as your guess can turn out to be wrong at any time.&lt;/p>
&lt;h4 id="faq-what">FAQ: WHAT?&lt;/h4>
&lt;p>&lt;strong>Answer&lt;/strong>: the LuaJIT interpreter, just like regular Lua, generates a different bytecode in these two cases. In the first case, space is allocated to place two array elements, which is logical.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ luac -l - &lt;&lt;&lt; "t1 = {1, 2}"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 [1] NEWTABLE 0 2 0 -- 2 in array, 0 in hash-map
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In the second case, one element in the array and one in the hashmap.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ luac -l - &lt;&lt;&lt; "t2 = {[2] = 2, 1}"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1 [1] NEWTABLE 0 1 1 -- 1 in array, 1 in hash-map
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But still, my primary concern as a developer is the correctness of my code. If LuaJIT is comfortable with presenting tables differently, it has the right to do it this way. For me, the main thing is that it is done seamlessly. So I suggest that we go over functions and spell out the expectations.&lt;/p>
&lt;h3 id="iterator-pairs">Iterator pairs()&lt;/h3>
&lt;p>Iterator pairs() has already been mentioned here earlier. It does not guarantee the iteration order. Even in an array. From the inside of LuaJIT, pairs() runs in sequence, first over the array, then over the hashmap. So if a numerical key gets to hashmap some way, then iteration order will be disturbed:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = table.new(4, 4)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for i = 1, 8 do t[i] = i end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x412c6df0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[5]: nil, 1, 2, 3, 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[4]: 7=7, 8=8, 5=5, 6=6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(pairs, t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 1=1, 2=2, 3=3, 4=4, 7=7, 8=8, 5=5, 6=6&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="faq-what-1">FAQ: WHAT?&lt;/h4>
&lt;p>&lt;strong>Answer&lt;/strong>: Function table.new(narr, nrec) pre-allocates the required storage space — this is the binding of the standard function lua_createtable(L, a, h) from Lua C API. If the table size is known beforehand (e.g. in the case of copying), this can be used to save on reallocations when the table is subsequently populated.&lt;/p>
&lt;p>This trick works exclusively due to pre-allocation. Another element being added to the table and the following re-hashing (which is a rather expensive and complex operation) will totally remove the veil of mystery from the table:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t[9] = 9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x411e1e30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[17]: nil, 1, 2, 3, 4, 5, 6, 7, 8, 9, nil, nil, nil, nil, nil, nil, nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[1]: nil=nil&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>By no means do I want to sound paranoid, and I consider this case highly implausible. In 99.9% of instances, “arrays” in Lua will really be represented by arrays, and the iteration order will be sequential.&lt;/p>
&lt;h3 id="table-length-tablegetn">Table length table.getn()&lt;/h3>
&lt;p>Much more often, errors occur because of array length being calculated incorrectly. The main thing one needs to know about it is this definition from &lt;a href="https://www.lua.org/manual/5.2/manual.html#3.4.6" target="_blank" rel="noopener noreferrer">Lua specification:&lt;/a>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">3.4.6 – The Length Operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The length of a table t is only defined if the table is a sequence, that is,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">the set of its positive numeric keys is equal to {1..n} for some non-negative
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">integer n. In that case, n is its length. Note that a table like
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {10, 20, nil, 40}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">is not a sequence, because it has the key 4 but does not have the key 3. (So,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">there is no n such that the set {1..n} is equal to the set of positive numeric
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">keys of that table.) Note, however, that non-numeric keys do not interfere with
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">whether a table is a sequence.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That is to say, not every table has the property of length. Surprise — undefined behavior can occur in Lua. Tables with “holes” simply don’t have such a property. In the LuaJIT implementation, &lt;a href="https://github.com/LuaJIT/LuaJIT/blob/v2.1/src/lj_tab.c#L640-L686" target="_blank" rel="noopener noreferrer">function lj_tab_len&lt;/a> is commented as follows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">** Try to find a boundary in table `t'. A `boundary' is an integer index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">*/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MSize LJ_FASTCALL lj_tab_len(GCtab *t);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>LuaJIT searches for a “boundary” in the table. If there are missing values in the array, and thus several boundaries exist, then, depending on the circumstances, LuaJIT will be able to find any of those — and will be right:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">print(#{nil, 2})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(#{[2] = 2})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="faq-what-2">FAQ: WHAT?&lt;/h4>
&lt;p>&lt;strong>Answer&lt;/strong>: These two tables differ in terms of internal representation.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tostring({nil, 2})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x410d5528
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[3]: nil, nil, 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[1]: nil=nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring({[2] = 2})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x410d5810
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[0]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[2]: nil=nil, 2=2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>LuaJIT searches for a boundary with a binary search, and it first checks the last element in the array. If it exists, the search proceeds to the hashmap, otherwise it continues in the array.&lt;/p>
&lt;p>And this definitely does not sound paranoid as there have been such errors in the past. Table length is implicitly used in other functions, so undefined behavior is something you don’t want to play with. To be fair, it should be noted that in arrays without holes, the behavior is strictly determined and does not depend on internal representation.&lt;/p>
&lt;h3 id="tablesort-sorting">Table.sort() sorting&lt;/h3>
&lt;p>Table sorting always works within the range from 1 to #t, and there is no way to influence this. So it is helpful to check input values in those functions which deal with a table as an array:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local function is_array(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if type(t) ~= 'table' then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local i = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for _, _ in pairs(t) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> i = i + 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if type(t[i]) == 'nil' then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Lua allows length to exist though, even when string keys are present, but as far as we are talking about typical contracts of functions, hybrid tables rarely work.&lt;/p>
&lt;h3 id="packunpack">&lt;strong>Pack/unpack&lt;/strong>&lt;/h3>
&lt;p>Where do those arrays with holes really come from? A rare weird case, as it might seem. But nope, there are dangers lurking every step of the way:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local function vararg(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> local args = {...}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -- #args == undefined behavior
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The multiple periods here are not an ellipsis but rather a designation of a variable number of function arguments in Lua.&lt;/p>
&lt;p>Then someone calls this function with arguments vararg(nil, “err”), and here you are: your function handles a holed array. If one calls unpack(t) after this, then the tail may fall off (or it may not, depends on luck, because this is UB).&lt;/p>
&lt;p>The &lt;a href="https://www.lua.org/manual/5.1/manual.html#pdf-unpack" target="_blank" rel="noopener noreferrer">Lua specification&lt;/a> reads:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">6.5 – Table Manipulation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unpack (list [, i [, j]])
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Returns the elements from the given table. This function is equivalent to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return list[i], list[i+1], ···, list[j]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">except that the above code can be written only for a fixed number of elements.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">By default, i is 1 and j is the length of the list, as defined by the length
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">operator #list.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To avoid the inadvertent loss of tail, instead of implicit unpack(t, 1, #t) always write explicitly unpack(t, 1, n). But where to take this n from? The answer depends on what kind of table you’ve got. In the case of varargs, you can use table.pack() method, which returns a hybrid:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = table.pack(nil, 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tostring(t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- table: 0x41053540
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- a[3]: nil, nil, 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- h[2]: nil=nil, n=2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(pairs, t)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 2=2, n=2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(unpack(t, 1, t.n))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- nil, 2 -- _It’s OK, it’s not UB_&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In LuaJIT (and in Tarantool), the table.pack function is inaccessible by default, but it can be enabled with flag of -DLUAJIT_ENABLE_LUA52COMPAT compiler. But it is even easier to implement this functionality manually:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">function table.pack(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return {..., n = select('#', ...)}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is where black magic comes into play — &lt;a href="https://www.lua.org/manual/5.1/manual.html#pdf-select" target="_blank" rel="noopener noreferrer">select(’#’, …)&lt;/a> — but a detailed description of its mechanism simply would not fit into this article. I’d just give you a tip: this has to do with &lt;a href="https://www.lua.org/manual/5.1/manual.html#3.1" target="_blank" rel="noopener noreferrer">Lua stack&lt;/a> — the mechanism which provides an interface between Lua and C (Lua C API). Meanwhile, we’ve got to move on.&lt;/p>
&lt;h3 id="iterator-ipairs">Iterator ipairs()&lt;/h3>
&lt;p>This one is a good operator, predictable enough. ipairs should be thought of as a shortened version of such while cycle as:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">local i = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">while type(t[i]) ~= 'nil' do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -- do something
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> i = i + 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>No internal representation differences can affect it, and “holes” don’t lead to undefined behavior — iteration simply stops.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">t = {1, 2, nil, 4}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(#t) -- UB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">traverse(ipairs, t) -- Not UB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- 1=1, 2=2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="pitfalls-of-ffi">Pitfalls of FFI&lt;/h3>
&lt;p>An attentive reader could have noticed that I have already used twice a strange expression type(x) ~= ’nil’. Why not just x == nil? This is because LuaJIT, unlike PUC-Rio Lua, has a magic type cdata:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ffi = require('ffi')
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NULL = ffi.new('void*', nil)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(type(NULL))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- cdata
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(type(nil))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- nil
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">print(NULL == nil)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">if NULL then print('NULL is not nil') end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- NULL is not nil&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This is a notorious pitfall in Tarantool named box.NULL. Note that condition if NULL is interpreted as true (unlike if nil), despite the fact that NULL == nil.&lt;/p>
&lt;p>Another pitfall in LuaJIT is the use of FFI types as a table key. This issue is not so frequently discussed in Lua manuals because there are no FFI types in the vanilla version of Lua. But LuaJIT can surprise you and backfire. Here’s what’s &lt;a href="https://github.com/LuaJIT/LuaJIT/blob/v2.1/doc/ext_ffi_semantics.html#L775-L789" target="_blank" rel="noopener noreferrer">written in the manual&lt;/a>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Lua tables may be indexed by cdata objects, but this doesn't provide any useful
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">semantics — cdata objects are unsuitable as table keys!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A cdata object is treated like any other garbage-collected object and is hashed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">and compared by its address for table indexing. Since there's no interning for
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cdata value types, the same value may be boxed in different cdata objects with
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">different addresses. Thus t[1LL+1LL] and t[2LL] usually do not point to the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">same hash slot and they certainly do not point to the same hash slot as t[2].&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Simply put, for cdata-keys, hash is counted from the pointer (i.e. void*), so they show unpredictable behavior and should be avoided in practice at all cost. A joke from Tarantool life:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> t = {1}; t[1ULL] = 2; t[1ULL] = 3;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 1: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t[1ULL]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I hope no one in one’s right mind would write things like this in code, but cdata is not infrequent, and this has occurred in the past. As an example, &lt;a href="https://www.tarantool.io/en/doc/2.2/reference/reference_lua/uuid/#uuid-call" target="_blank" rel="noopener noreferrer">consider uuid&lt;/a> and clock.time64() from Tarantool. Or big values stored in spaces in unsigned format. As long as we are on this subject, note that it’s possible to obtain unexpected results without using too, though I’ve never seen this in practice:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">tarantool> t = {'normal one'}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> t[1.0 + 2^-52] = '1.0 + 2^-52'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> t[0.1 + 0.3*3] = '0.1 + 0.3*3'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tarantool> t
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 1: normal one
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 1.0 + 2^-52
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: 0.1 + 0.3*3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;ul>
&lt;li>Table length operator #t is only defined for arrays without holes. All the rest is Undefined Behavior.&lt;/li>
&lt;li>Function ipairs() is iterated as while type(t[i]) ~= ’nil’ — not for all keys, but, on the plus side, in a predictable way, and the order is guaranteed.&lt;/li>
&lt;li>Function pairs() is iterated for all keys, but the iteration order is affected by the internal representation of table.&lt;/li>
&lt;li>Functions unpack, table.sort, table.insert, and table.remove, once called with default arguments, hold undefined behavior in them due to implicit #t.&lt;/li>
&lt;li>The use of “strange” (ffi) values in keys affords you plenty of ways to shoot yourself in the foot. They should be avoided.&lt;/li>
&lt;/ul>
&lt;p>– &lt;em>The content in this blog is provided in good faith by members of the open source community. Percona has not edited or tested the technical content. Views expressed are the authors’ own. When using the advice from this or any other online resource test ideas before applying them to your production systems, and always secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Yaroslav Dynnikov</author><category>rosik</category><category>Advanced Level</category><category>Lua</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_3ecdb5f4e60ab1c8.jpg"/><media:content url="https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa_hu_9ae2afc51306a71.jpg" medium="image"/></item><item><title>Unexpected slow ALTER TABLE in MySQL 5.7</title><link>https://percona.community/blog/2020/04/23/unexpected-slow-alter-table-mysql-5-7/</link><guid>https://percona.community/blog/2020/04/23/unexpected-slow-alter-table-mysql-5-7/</guid><pubDate>Thu, 23 Apr 2020 15:47:21 UTC</pubDate><description>Usually one would expect that ALTER TABLE with ALGORITHM=COPY will be slower than the default ALGORITHM=INPLACE. In this blog post we describe the case when this is not so.</description><content:encoded>&lt;p>Usually one would expect that ALTER TABLE with ALGORITHM=COPY will be slower than the default ALGORITHM=INPLACE. In this blog post we describe the case when this is not so.&lt;/p>
&lt;p>One of the reasons for such behavior is the lesser known limitation of ALTER TABLE (with default ALGORITHM=INPLACE) that avoids REDO operations. As a result, all dirty pages of the altered table/tablespace have to be flushed before the ALTER TABLE completion.&lt;/p>
&lt;h2 id="some-history">Some history&lt;/h2>
&lt;p>A long time ago, all “ALTER TABLE” (DDLs) operations in MySQL were implemented by creating a new table with the new structure, then copying the content of the original table to the new table, and finally renaming the table. During this operation the table was locked to prevent data inconsistency.&lt;/p>
&lt;p>Then, for InnoDB tables, the new algorithms were introduced, which do not involve the full table copy and some operations do not apply the table level lock – first the online add index algorithm was introduced for InnoDB, then the non-blocking add columns or &lt;em>online DDLs&lt;/em>. For the list of all online DDLs in MySQL 5.7 you can refer to this &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html" target="_blank" rel="noopener noreferrer">document&lt;/a>.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>Online DDLs are great for common operations like add/drop a column, &lt;strong>however we have found out that these can be significantly slower&lt;/strong>. For example, adding a field to a large table on a “beefy” server with 128G of RAM can take unexpectedly long time.&lt;/p>
&lt;p>In one of our “small” Percona Servers, it took a little more than 5 min to add a column to the 13 GB InnoDB table. Yet on another “large” Percona Server, where the same table was 30 GB in size, it took more than 4 hours to add the same column.&lt;/p>
&lt;h3 id="investigating-the-issue">Investigating the issue&lt;/h3>
&lt;p>After verifying that the disk I/O throughput is the same on both servers, we investigated the reason for such a large difference in the duration of ALTER TABLE helios ADD COLUMN query using &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)&lt;/a> to record and review performance.&lt;/p>
&lt;p>On the smaller server, where ALTER TABLE was faster, the relevant PMM monitoring plots show:
&lt;figure>&lt;img width="468" height="209" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/faster-alter-table_hu_e40bbadebd3e31f6.webp 468w" src="https://percona.community/blog/2020/04/faster-alter-table_hu_e40bbadebd3e31f6.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In our Percona Server version 5.7, ALTER TABLE helios ADD COLUMN  was executed in place. On the left, we can observe a steady rate of the table rebuild, followed by four spikes corresponding to rebuilding of the four indices.&lt;/p>
&lt;p>What is also interesting is that ALTER TABLE with the INPLACE ALGORITHM (which will be the default for adding a field) &lt;strong>will need to force flushing of all dirty pages and wait until it is done&lt;/strong>. This is a much less known fact and very sparsely documented. The reason for this is that undo and redo logging is disabled for this operation:&lt;/p>
&lt;blockquote>
&lt;p>No undo logging or associated redo logging is required for ALGORITHM=INPLACE. These operations add overhead to DDL statements that use ALGORITHM=COPY. &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>In this situation the only option is to flush all dirty pages, otherwise the data can become inconsistent. There’s a special treatment to be seen for ALTER TABLE in &lt;a href="https://github.com/percona/percona-server/blob/5.7/storage/innobase/buf/buf0flu.cc#L3907" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a>.&lt;/p>
&lt;p>Back to our situation – during table rebuild, InnoDB buffer pool becomes increasingly dirty:
&lt;figure>&lt;img width="468" height="210" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/increasingly-dirty-buffer-pool-1_hu_4f371eff377877f6.webp 468w" src="https://percona.community/blog/2020/04/increasingly-dirty-buffer-pool-1_hu_4f371eff377877f6.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The graph shows peak at about 9 GB corresponding to the table data size. Originally we were under the impression that as dirty pages are flushed to disk, the in-memory dirty pages volume decreases at the rate determined by the Percona adaptive flushing algorithm. It turns out that flushing by ALTER and adaptive flushing have no relation: both happen concurrently. Flushing by ALTER is single page flushing and is done by iterating pages in the flush list and flushing pages of desired space_id (one by one). That probably explains that if the server has more RAM it can be slower to flush as it will have to scan a larger list.&lt;/p>
&lt;p>After the last buffer pool I/O request (from the last index build) ends, the algorithm increases the rate of flushing for the remaining dirty pages. The ALTER TABLE finishes when there are no more dirty pages left in the memory.&lt;/p>
&lt;p>You can see the six-fold increase in the I/O rate clearly in the plot below:
&lt;figure>&lt;img width="468" height="172" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/six-fold-increase_hu_ac19c4c051438682.webp 468w" src="https://percona.community/blog/2020/04/six-fold-increase_hu_ac19c4c051438682.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In contrast, on the “large” server, ALTER TABLE behaved differently. Although, at the beginning it proceeded the similar way:
&lt;figure>&lt;img width="468" height="205" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/alter-table-different-on-larger-database_hu_4e3d080ac70dce1c.webp 468w" src="https://percona.community/blog/2020/04/alter-table-different-on-larger-database_hu_4e3d080ac70dce1c.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>On the left, we can observe a steady rate of the table rebuild, followed by four spikes corresponding to rebuilding of the four table indices. During table rebuild the buffer pool became increasingly dirty:
&lt;figure>&lt;img width="468" height="203" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/table-rebuild-increasingly-dirty_hu_bf689b3f060e03b4.webp 468w" src="https://percona.community/blog/2020/04/table-rebuild-increasingly-dirty_hu_bf689b3f060e03b4.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Followed by the 21 GB of the table data, there are four kinks corresponding to four indices builds. It takes about twenty minutes to complete this part of ALTER TABLE processing of the 30 GB table. To some degree this is comparable to about four minutes to complete the similar part of ALTER TABLE processing of the 13 GB table. However, the adaptive flushing algorithm behaved differently on that server. It took more than four hours to complete the dirty pages flushing from memory
&lt;figure>&lt;img width="468" height="203" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/time-to-clear-pages_hu_d1b57e72e1d7379f.webp 468w" src="https://percona.community/blog/2020/04/time-to-clear-pages_hu_d1b57e72e1d7379f.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is because in contrast to the “small” server, the buffer pool I/O remained extremely low:
&lt;figure>&lt;img width="468" height="188" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/low-buffer-pool-io_hu_7bef02fbc05eb976.webp 468w" src="https://percona.community/blog/2020/04/low-buffer-pool-io_hu_7bef02fbc05eb976.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>This is not a hardware limitation, as PMM monitoring shows that at other times, the “large” server demonstrated ten times higher buffer pool I/O rates, e.g.:
&lt;figure>&lt;img width="468" height="177" sizes="(max-width: 468px) 100vw, 468px" srcset="https://percona.community/blog/2020/04/high-buffer-pool-io_hu_a4b08aedf706585e.webp 468w" src="https://percona.community/blog/2020/04/high-buffer-pool-io_hu_a4b08aedf706585e.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Beware the slower performance of ALTER TABLE … ADD COLUMN (default algorithm is INPLACE). On the large server the difference can be significant: the smaller the buffer pool the smaller is the flush lists and faster the flushing as the ALTER table has a smaller flush_lists to iterate. In some cases it may be better (and with more predictable timing) to use ALTER TABLE ALGORITHM=COPY.&lt;/p>
&lt;h3 id="about-virtualhealth">About VirtualHealth&lt;/h3>
&lt;p>VirtualHealth created HELIOS, the first SaaS solution purpose-built for value-based healthcare. Utilized by some of the most innovative health plans in the country to manage millions of members, HELIOS streamlines person-centered care with intelligent case and disease management workflows, unmatched data integration, broad-spectrum collaboration, patient engagement, and configurable analytics and reporting. Named one of the fastest-growing companies in North America by Deloitte in 2018 and 2019, VirtualHealth empowers healthcare organizations to achieve enhanced outcomes, while maximizing efficiency, improving transparency, and lowering costs. For more information, visit &lt;a href="http://www.virtualhealth.com/" target="_blank" rel="noopener noreferrer">www.virtualhealth.com&lt;/a>.&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. Percona has not edited or tested the technical content. Views expressed are the authors’ own. When using the advice from this or any other online resource test ideas before applying them to your production systems, and always secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Alexander Rubin</author><author>Alexandre Vaniachine</author><category>Intermediate Level</category><category>MySQL</category><category>Percona Server for MySQL</category><category>performance</category><media:thumbnail url="https://percona.community/blog/2020/04/alter-table-different-on-larger-database_hu_290194340bced2df.jpg"/><media:content url="https://percona.community/blog/2020/04/alter-table-different-on-larger-database_hu_b5b7f0161f8a7687.jpg" medium="image"/></item><item><title>Our Offer to Online Meetups and Community Leaders</title><link>https://percona.community/blog/2020/04/07/our-offer-to-online-meetups-and-community-leaders/</link><guid>https://percona.community/blog/2020/04/07/our-offer-to-online-meetups-and-community-leaders/</guid><pubDate>Tue, 07 Apr 2020 10:52:51 UTC</pubDate><description> Percona’s Community team organizes our speakers at in-person events around the world, such as Percona Live, Percona University, and events sponsored by other organizations. However, like everyone else around the world, all our plans are on hold due to the Coronavirus pandemic.</description><content:encoded>&lt;p>
&lt;figure>&lt;img width="1050" height="585" sizes="(max-width: 1050px) 100vw, 1050px" srcset="https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_89b110463ba2c94a.webp 768w, https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_23b90ae4c04bca3.webp 480w, https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_6baf7445e5702e09.webp 1050w" src="https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_6baf7445e5702e09.webp" alt="Offer of Percona Speakers for events" loading="lazy" />&lt;/figure>Percona’s Community team organizes our speakers at in-person events around the world, such as Percona Live, Percona University, and events sponsored by other organizations. However, like everyone else around the world, all our plans are on hold due to the Coronavirus pandemic.&lt;/p>
&lt;p>Perhaps you, like many others, are organizing online events, such as &lt;a href="https://help.meetup.com/hc/en-us/articles/360040609112" target="_blank" rel="noopener noreferrer">virtual meetups on Meetup.com&lt;/a>. We can help you by making Percona’s team of experienced and well-known speakers available for your event. We have experts on key open-source database topics, including Kubernetes, monitoring, high availability, and more.&lt;/p>
&lt;p>Many of our speakers have spoken at major tech conferences before. These include experts like &lt;a href="https://www.linkedin.com/in/peterzaitsev/" target="_blank" rel="noopener noreferrer">Peter Zaitsev&lt;/a>, &lt;a href="https://www.linkedin.com/in/askdba/" target="_blank" rel="noopener noreferrer">Alkin Tezuysal&lt;/a>, &lt;a href="https://www.linkedin.com/in/ibrarahmed74/" target="_blank" rel="noopener noreferrer">Ibrar Ahmed&lt;/a>, &lt;a href="https://www.linkedin.com/in/tylerduzan/" target="_blank" rel="noopener noreferrer">Tyler Duzan&lt;/a>, and &lt;a href="https://www.linkedin.com/in/svetsmirnova/" target="_blank" rel="noopener noreferrer">Sveta Smirnova&lt;/a>, with availability across many timezones. Further, if you invite a Percona speaker to present virtually, Percona will help promote your events on our blog and social networks.&lt;/p>
&lt;p>To get started, just email &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> anytime.&lt;/p>
&lt;h2 id="percona-live-amsterdam-2019">
&lt;figure>&lt;img width="1024" height="768" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://percona.community/blog/2020/04/ple-1_hu_6b00785bb75b3e7.webp 768w, https://percona.community/blog/2020/04/ple-1_hu_cd0bc3fb2aa6c6fd.webp 480w, https://percona.community/blog/2020/04/ple-1_hu_3806155f700d9c44.webp 1024w" src="https://percona.community/blog/2020/04/ple-1_hu_3806155f700d9c44.webp" alt="Percona Live Amsterdam 2019" loading="lazy" />&lt;/figure>
Percona Live Amsterdam 2019&lt;/h2>
&lt;p>&lt;em>Desk Photo by: &lt;a href="https://burst.shopify.com/@sarahpflugphoto" target="_blank" rel="noopener noreferrer">Sarah Pflug&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>daniil.bazhenov</category><category>Events</category><category>Information</category><category>online meetups</category><category>percona speakers</category><media:thumbnail url="https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_6db2be1eb222853b.jpg"/><media:content url="https://percona.community/blog/2020/04/online-meetups-percona-linkedin_hu_8e18047ade9254f6.jpg" medium="image"/></item><item><title>Deploying Tarantool Cartridge Applications with Zero Effort (Part 2)</title><link>https://percona.community/blog/2020/04/01/deploying-tarantool-cartridge-applications-with-zero-effort-part-2/</link><guid>https://percona.community/blog/2020/04/01/deploying-tarantool-cartridge-applications-with-zero-effort-part-2/</guid><pubDate>Wed, 01 Apr 2020 11:04:45 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="1000" height="500" sizes="(max-width: 1000px) 100vw, 1000px" srcset="https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_5e9b59e4ce31d557.webp 768w, https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_a01370337678473b.webp 480w, https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_7e0da98393b9bad2.webp 1000w" src="https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_7e0da98393b9bad2.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>We have recently &lt;a href="https://www.percona.com/community-blog/2020/03/24/deploying-tarantool-cartridge-applications-with-zero-effort-part-1/" target="_blank" rel="noopener noreferrer">talked&lt;/a> about how to deploy a &lt;a href="https://habr.com/ru/company/mailru/blog/470812/" target="_blank" rel="noopener noreferrer">Tarantool Cartridge&lt;/a> application. However, an application’s life doesn’t end with deployment, so today we will update our application and figure out how to manage topology, sharding, and authorization, and change the role configuration.&lt;/p>
&lt;p>Feeling interested? Please continue reading under the cut.&lt;/p>
&lt;h3 id="where-did-we-leave-off">Where did we leave off?&lt;/h3>
&lt;p>Last time, we set up the following topology:
&lt;figure>&lt;img width="2612" height="1562" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_3b995d88ad1ac013.webp 768w, https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_cd799e2ae0ad2a58.webp 480w, https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_5600a977586e29d1.webp 1400w" src="https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_5600a977586e29d1.webp" alt=" " loading="lazy" />&lt;/figure>
The sample &lt;a href="https://github.com/dokshina/deploy-tarantool-cartridge-app" target="_blank" rel="noopener noreferrer"> repository &lt;/a> has changed a bit: there are new files called &lt;code>getting-started-app-2.0.0-0.rpm&lt;/code> and &lt;code>hosts.updated.2.yml&lt;/code> . You do not have to pull the new version, you can just download the package by clicking this &lt;a href="https://github.com/dokshina/deploy-tarantool-cartridge-app/blob/2.0.0/getting-started-app-2.0.0-0.rpm" target="_blank" rel="noopener noreferrer">link&lt;/a>, and you need &lt;code>hosts.updated.2.yml&lt;/code> only to look there if you have trouble changing the current inventory.&lt;/p>
&lt;p>If you have followed all the steps from the previous part of this tutorial, you now have a cluster configuration with two storage replica sets in the hosts.yml  file (&lt;code>hosts.updated.yml&lt;/code> in the repository).&lt;/p>
&lt;p>First, start the virtual machines:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vagrant up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You should already have an up to date version of the Tarantool Cartridge Ansible role installed. Just in case, run the following command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-galaxy install tarantool.cartridge,1.1.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So, the current cluster configuration:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # common cluster variables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_app_name: getting-started-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_package_path: ./getting-started-app-1.0.0-0.rpm # path to package
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_cluster_cookie: app-default-cookie # cluster cookie
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # common ssh options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_ssh_private_key_file: ~/.vagrant.d/insecure_private_key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_ssh_common_args: '-o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # INSTANCES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8181
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8182
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3302'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8183
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3303'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8184
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3302'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8185
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY MACHINES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # first machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the first machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # second machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the second machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY REPLICA SETS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_app_1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replica set configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: app-1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - app-1 # leader
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'api'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replica set instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replica set configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: storage-1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> weight: 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-1 # leader
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-1-replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'storage'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replica set instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replicaset configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> weight: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2-replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'storage'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replicaset instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica:&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Go to &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and make sure that your cluster is operating correctly.&lt;/p>
&lt;p>As before, we change this file step-by-step and watch how the cluster changes. You can always look up the final version in &lt;code>hosts.updated.2.yml&lt;/code>.&lt;/p>
&lt;p>Let’s start!&lt;/p>
&lt;h3 id="updating-the-application">Updating the application&lt;/h3>
&lt;p>First, we are going to update our application. Make sure you have the &lt;code>getting-started-app-2.0.0-0.rpm&lt;/code> file in your current directory (otherwise, &lt;a href="https://github.com/dokshina/deploy-tarantool-cartridge-app/blob/2.0.0/getting-started-app-2.0.0-0.rpm" target="_blank" rel="noopener noreferrer">download&lt;/a> it from the repository).&lt;/p>
&lt;p>Specify the path to a new version of the package:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_app_name: getting-started-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_package_path: ./getting-started-app-2.0.0-0.rpm # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_enable_tarantool_repo: false # &lt;==&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We have set &lt;code>cartridge_enable_tarantool_repo: false&lt;/code> so that the role does not include the repository with the Tarantool package that we had already installed last time. It slightly speeds up the deployment process but it isn’t obligatory.
Run the playbook with the &lt;code>cartridge-instances&lt;/code> tag:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-instances&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And check that the package has been updated:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vagrant ssh vm1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[vagrant@svm1 ~]$ sudo yum list installed | grep getting-started-app&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Check that the version is 2.0.0 :&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">getting-started-app.x86_64 2.0.0-0 installed&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now you can safely try out the new version of the application.&lt;/p>
&lt;h3 id="enabling-sharding">Enabling sharding&lt;/h3>
&lt;p>Let’s enable sharding so that we can later get to managing &lt;code>storage&lt;/code> replica sets. It’s an easy thing to do. Add the &lt;code>cartridge_bootstrap_vshard&lt;/code>  variable to the &lt;code>all.vars&lt;/code>  section:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_cluster_cookie: app-default-cookie # cluster cookie
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_bootstrap_vshard: true # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note that we have specified the &lt;code>cartridge-config&lt;/code> tag to run only the tasks related to the cluster configuration.&lt;/p>
&lt;p>Open the Web UI &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and note that the buckets are distributed among storage replica sets as 2:3  (as you may recall, we specified these weights for the replica sets):&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2608" height="1562" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_52e4cf0699fd6d9b.webp 768w, https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_b91d3e17c84d29fe.webp 480w, https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_96233cc0d1117f95.webp 1400w" src="https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_96233cc0d1117f95.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="enabling-automatic-failover">Enabling automatic failover&lt;/h3>
&lt;p>Now we are going to enable the automatic failover mode in order to find out what it is and how it works. Add the &lt;code>cartridge_failover&lt;/code> flag to the configuration:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_cluster_cookie: app-default-cookie # cluster cookie
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_bootstrap_vshard: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_failover: true # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Start cluster management tasks again:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>When the playbook finishes successfully, you can go to the Web UI and make sure that the &lt;code>Failover&lt;/code> switch in the top right corner is now switched on. To disable the automatic failover mode, simply change the value of &lt;code>cartridge_failover&lt;/code> to &lt;code>false&lt;/code> and run the playbook again.&lt;/p>
&lt;p>Now let’s take a closer look at this mode and see why we enabled it.&lt;/p>
&lt;h3 id="looking-into-failover">Looking into failover&lt;/h3>
&lt;p>You have probably noticed the &lt;code>failover_priority&lt;/code> variable that we specified for each replica set. Let’s look into it.&lt;/p>
&lt;p>Tarantool Cartridge provides an automatic failover mode. Each replica set has a leader, that is, the instance where the record is written. If anything happens to the leader, one of the replicas takes over its role. Which one? Look at the &lt;code>storage-2&lt;/code> replica set:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2-replica&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In &lt;code>failover_priority&lt;/code>, we specified the &lt;code>storage-2&lt;/code> instance as the first one. In the Web UI, it is the first one in the replica set instance list and is marked with a green crown. This is the leader, or the first instance specified in failover_priority :&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1566" height="498" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_fd327f702388a3e9.webp 768w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_3a2c1b466cd8af1c.webp 480w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_4b8b6dec51611d29.webp 1400w" src="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_4b8b6dec51611d29.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Now let’s see what happens if something is wrong with the replica set leader. Go to the virtual machine and stop the &lt;code>storage-2&lt;/code> instance:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vagrant ssh vm2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[vagrant@vm2 ~]$ sudo systemctl stop getting-started-app@storage-2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Back to the Web UI:
&lt;figure>&lt;img width="1534" height="482" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_7178a261c89d368c.webp 768w, https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_59b9dda09164f8f8.webp 480w, https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_96de09df1593c5b8.webp 1400w" src="https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_96de09df1593c5b8.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>The crown of the &lt;code>storage-2&lt;/code> instance turns red, which means that the assigned leader is unhealthy. But &lt;code>storage-2-replica&lt;/code> now has a green crown, so this instance took over the leader role until &lt;code>storage-2&lt;/code> comes back into operation. This is the automatic failover in action.&lt;/p>
&lt;p>Let’s bring &lt;code>storage-2&lt;/code> back to life:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-13">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vagrant ssh vm2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[vagrant@vm2 ~]$ sudo systemctl start getting-started-app@storage-2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Everything is back to normal:
&lt;figure>&lt;img width="1566" height="498" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_fd327f702388a3e9.webp 768w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_3a2c1b466cd8af1c.webp 480w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_4b8b6dec51611d29.webp 1400w" src="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_4b8b6dec51611d29.webp" alt=" " loading="lazy" />&lt;/figure>
Now we change the instance order in failover priority. We make &lt;code>storage-2-replica&lt;/code> the leader and remove &lt;code>storage-2&lt;/code> from the list:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-14">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replicaset configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2-replica # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run cartridge-replicasets  tasks for instances from the replicaset_storage_2  group:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-15">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit replicaset_storage_2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Go to &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and check that the leader has changed:
&lt;figure>&lt;img width="1562" height="498" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_e749bd3853639b58.webp 768w, https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_93efb2974ae88b36.webp 480w, https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_2c8f5ee0f20637d2.webp 1400w" src="https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_2c8f5ee0f20637d2.webp" alt=" " loading="lazy" />&lt;/figure> But we removed the &lt;code>storage-2&lt;/code> instance from the configuration, why is it still here? The fact is that when Cartridge receives a new &lt;code>failover_priority&lt;/code>  value at the input, it arranges the instances as follows: the first instance from the list becomes the leader followed by the other specified instances. Instances left out from &lt;code>failover_priority&lt;/code> are arranged by UUID and added to the end.&lt;/p>
&lt;h3 id="expelling-instances">Expelling instances&lt;/h3>
&lt;p>What if you want to expel an instance from the topology? It is straightforward: just assign the expelled flag to it. Let’s expel the &lt;code>storage-2-replica&lt;/code>  instance. It is the leader now, so Cartridge will not let us do this. But we’re not afraid so we’ll try:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-16">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3302'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8185
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expelled: true # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We specify the cartridge-replicasets  tag because expelling an instance is a change in topology:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-17">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit replicaset_storage_2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Run the playbook and observe the error:
&lt;figure>&lt;img width="1438" height="174" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_5543ac4b4f28d44c.webp 768w, https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_3f6a7cd317bb072d.webp 480w, https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_c3915105bc32336c.webp 1400w" src="https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_c3915105bc32336c.webp" alt=" " loading="lazy" />&lt;/figure>
Cartridge doesn’t let the current replica set leader be removed from the topology. This makes good sense because the replication is asynchronous, so expelling the leader is likely to cause data loss. We need to specify another leader and only then expel the instance. The role first applies the new replica set configuration and then proceeds to expelling the instance. So we change the &lt;code>failover_priority&lt;/code> and run the playbook again:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-18">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replicaset configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2 # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-19">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit replicaset_storage_2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And so &lt;code>storage-2-replica&lt;/code> disappears from the topology!
&lt;figure>&lt;img width="1562" height="350" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_6785b60b9795c0a7.webp 768w, https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_426d6c4104fcd343.webp 480w, https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_328b5b04ca787656.webp 1400w" src="https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_328b5b04ca787656.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Please note that the instance is expelled permanently and irrevocably. After removing the instance from the topology, our Ansible role stops the systemd service and deletes all the files of this instance.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1680" height="362" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_5908fae61323cc47.webp 768w, https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_89558e906278678a.webp 480w, https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_5d0d18c8f2a4776e.webp 1400w" src="https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_5d0d18c8f2a4776e.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>If you suddenly change your mind and decide that the &lt;code>storage-2&lt;/code> replica set still needs a second instance, you will not be able to restore it. Cartridge remembers the UUIDs of all the instances that have left the topology and will not allow the expelled one to return. You can start a new instance with the same name and configuration, but its UUID will obviously be different, so Cartridge will allow it to join.&lt;/p>
&lt;h3 id="deleting-replica-sets">Deleting replica sets&lt;/h3>
&lt;p>We have already found out that the replica set leader cannot be expelled. But what if we want to remove &lt;code>thestorage-2&lt;/code> replica set permanently? Of course, there is a solution. In order not to lose the data, we must first transfer all the buckets to &lt;code>storage-1&lt;/code> . For this purpose, we set the weight of the &lt;code>storage-2&lt;/code> replica set to 0 :&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-20">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replicaset configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> weight: 0 # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Start the topology control tasks:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-21">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit replicaset_storage_2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Open the Web UI &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and watch all the buckets flow into storage-1 :
&lt;figure>&lt;img width="2746" height="1442" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_e5e4135928a78df0.webp 768w, https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_1af599706e201dd7.webp 480w, https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_7f34009dbeb6b739.webp 1400w" src="https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_7f34009dbeb6b739.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Assign the expelled flag to the &lt;code>storage-2&lt;/code> leader and say goodbye to this replica set:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-22">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3303'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8184
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expelled: true # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-23">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note that we did not specify the &lt;code>limit&lt;/code> option this time since at least one of the instances with the running playbook must not be marked as &lt;code>expelled&lt;/code>. So we’re back to the original topology:
&lt;figure>&lt;img width="2594" height="1338" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_bacca38c194a69cd.webp 768w, https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_d2950b602499185e.webp 480w, https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_f98cc9fb3da84455.webp 1400w" src="https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_f98cc9fb3da84455.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="authorization">Authorization&lt;/h3>
&lt;p>Let’s take our minds off replica set control and think about safety. Now any unauthorized user can manage the cluster via Web UI. We have to admit; it doesn’t look too good.&lt;/p>
&lt;p>With Cartridge, you can connect your own authorization module, such as LDAP (or whatever), and use it to manage users and their access to the application. But here we’ll be using the built-in authorization module that Cartridge uses by default. This module allows you to perform basic operations with users (delete, add, edit) and implements password verification.&lt;/p>
&lt;p>Please note that our Ansible role requires the authorization backend to implement all these functions.&lt;/p>
&lt;p>Okay, we need to put theory into practice now. First, we are going to make authorization mandatory, set the session parameters, and add a new user:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-24">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # authorization
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_auth: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> enabled: true # enable authorization
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cookie_max_age: 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cookie_renew_age: 100
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> users: # cartridge users to set up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - username: dokshina
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password: cartridge-rullez
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> fullname: Elizaveta Dokshina
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> email: dokshina@example.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # deleted: true # uncomment to delete user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Authorization is managed within the cartridge-config  tasks, so specify this tag:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-25">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> has a surprise for you:
&lt;figure>&lt;img width="1904" height="900" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_380ef065ab07ad79.webp 768w, https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_86a90f668c3221ea.webp 480w, https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_e778874714ef5f57.webp 1400w" src="https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_e778874714ef5f57.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You can log in with the username and password of the new user, or as admin , the default user. The password is a cluster cookie; we have specified this value in the cartridge_cluster_cookie  variable (it is app-default-cookie , don’t bother to check).&lt;/p>
&lt;p>After a successful login, we open the Users tab to make sure that everything goes well:
&lt;figure>&lt;img width="2746" height="512" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_c2fa2152666325bc.webp 768w, https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_24e196ee8f52140f.webp 480w, https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_b43bf0ae47ef53b8.webp 1400w" src="https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_b43bf0ae47ef53b8.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Try adding new users and changing their parameters. To delete a user, specify the deleted: true  flag for that user. The email and fullname values are not used by Cartridge, but you can specify them for your convenience.&lt;/p>
&lt;h3 id="application-configuration">Application configuration&lt;/h3>
&lt;p>Let’s step back and skim through the whole story.&lt;/p>
&lt;p>We have deployed a small application that stores data about customers and their bank accounts. As you may recall, this application has two implemented roles: api and storage . The storage role deals with data storage and sharding using the integrated vshard-storage  role. The second role (or api ) implements an HTTP server with an API for data management. It also has another integral standard role (vshard-router ) that controls sharding.&lt;/p>
&lt;p>So, we send the first request to the application API to add a new client:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-26">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -X POST -H "Content-Type: application/json"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -d '{"customer_id":1, "name":"Elizaveta", "accounts":[{"account_id": 1}]}'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http://localhost:8182/storage/customers/create&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In return, we get something like this:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-27">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{"info":"Successfully created"}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note that in the URL we have specified the 8082 port of the app-1  instance as this is the port for the API.&lt;/p>
&lt;p>Now we update the balance of the new user:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-28">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -X POST -H "Content-Type: application/json"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -d '{"account_id": 1, "amount": "1000"}'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http://localhost:8182/storage/customers/1/update_balance&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We see the updated balance in the response:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-29">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{"balance":"1000.00"}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>All right, it works! The API is implemented, Cartridge takes care of data sharding, we have already configured the failover priority in case of emergency and enabled authorization. It’s time to get down to configuring the application.&lt;/p>
&lt;p>The current cluster configuration is stored in a distributed configuration file. Each instance stores a copy of this file, and Cartridge ensures that it is synchronized among all the nodes in the cluster. We can specify the role configuration of our application in this file, and Cartridge will make sure that the new configuration is distributed across all the instances.&lt;/p>
&lt;p>Let’s take a look at the current contents of this file. Go to the Configuration files  tab and click on the Download button:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="1584" height="560" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_72958ea2fafba5e2.webp 768w, https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_a46b0d7dc87ee052.webp 480w, https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_8d38e3e6e28142fe.webp 1400w" src="https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_8d38e3e6e28142fe.webp" alt=" " loading="lazy" />&lt;/figure> In the downloaded &lt;code>config.yml&lt;/code> file, we find an empty table. It’s no surprise because we haven’t specified any parameters yet:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-30">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">--- []
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In fact, the cluster configuration file is not empty: it stores the current topology, authorization settings, and sharding parameters. Cartridge does not share this information so easily; the file is intended for internal use, and therefore stored in hidden system sections that you cannot edit.&lt;/p>
&lt;p>Each application role can use one or more configuration sections. The new configuration is loaded in two steps. First, all the roles verify that they are ready to accept the new parameters. If there are no problems, the changes are applied; otherwise, the changes are rolled back.&lt;/p>
&lt;p>Now get back to the application. The api role uses the max-balance  section, where the maximum allowed balance for a single client account is stored. Let’s configure this section using our Ansible role (not manually, of course).&lt;/p>
&lt;p>So now the application configuration (more precisely, the available part) is an empty table. Now add a max-balance  section there with a value of 100000 , and specify the cartridge_app_config  variable in the inventory file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-31">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # cluster-wide config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_app_config: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max-balance: # section name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> body: 1000000 # section body
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # deleted: true # uncomment to delete section max-balance
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We have specified a section name (max-balance ) and its contents (body ). The content of the section can be more than just a number; it can also be a table or a string depending on how the role is written and what type of value you want to use. Run:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-32">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And check that the maximum allowed balance has indeed changed:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-33">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ curl -X POST -H "Content-Type: application/json"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -d '{"account_id": 1, "amount": "1000001"}'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http://localhost:8182/storage/customers/1/update_balance&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In return, we get an error, just as we wanted:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-34" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-34">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{"info":"Error","error":"Maximum is 1000000"}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>You can download the configuration file from the Configuration files  tab once again to make sure the new section is there:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-35" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-35">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max-balance: 1000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Try adding new sections to the application configuration, change their contents, or delete them altogether (to do this, you need to set the &lt;code>deleted: true&lt;/code> flag in the section):&lt;/p>
&lt;p>For more information on using the distributed configuration in roles, see the Tarantool Cartridge &lt;a href="https://www.tarantool.io/en/rocks/cartridge/1.0/modules/cartridge.clusterwide-config/" target="_blank" rel="noopener noreferrer">documentation&lt;/a>.&lt;/p>
&lt;p>Don’t forget to run vagrant halt to stop the virtual machines when you’re done.&lt;/p>
&lt;h3 id="summary">Summary&lt;/h3>
&lt;p>Last time we learned how to deploy distributed Tarantool Cartridge applications using a special Ansible role. Today we updated the application and learned how to manage application topology, sharding, authorization, and configuration.&lt;/p>
&lt;p>As a next step, you can try &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html" target="_blank" rel="noopener noreferrer">different approaches&lt;/a> to writing Ansible Playbook and use your apps in the most convenient way.&lt;/p>
&lt;p>If something doesn’t work or you have ideas on how to improve our Ansible role, please feel free to create a &lt;a href="https://github.com/tarantool/ansible-cartridge/issues/new" target="_blank" rel="noopener noreferrer">ticket&lt;/a>. We are always happy to help and open to any ideas and suggestions!&lt;/p></content:encoded><author>Elizaveta Dokshina</author><category>Open Source Databases</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_936479fd62c5e5fc.jpeg"/><media:content url="https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0_hu_1cc17abbb217f9e7.jpeg" medium="image"/></item><item><title>Deploying Tarantool Cartridge Applications with Zero Effort (Part 1)</title><link>https://percona.community/blog/2020/03/24/deploying-tarantool-cartridge-applications-with-zero-effort-part-1/</link><guid>https://percona.community/blog/2020/03/24/deploying-tarantool-cartridge-applications-with-zero-effort-part-1/</guid><pubDate>Tue, 24 Mar 2020 14:04:32 UTC</pubDate><description>Tarantool is an open-source in-memory DB with a Lua application server on board. It’s best used for apps that require high performance and horizontal scaling. Out of the box we support horizontal scaling via the vshard module. There are quite a few things that you have to keep in mind when you work on your business logic, though. Not ideal.</description><content:encoded>&lt;p>Tarantool is an open-source in-memory DB with a Lua application server on board. It’s best used for apps that require high performance and horizontal scaling. Out of the box we support horizontal scaling via the &lt;a href="https://github.com/tarantool/vshard" target="_blank" rel="noopener noreferrer">vshard&lt;/a> module. There are quite a few things that you have to keep in mind when you work on your business logic, though. Not ideal.&lt;/p>
&lt;p>We made it easier. All the lessons learned from creating distributed apps resulted in a framework called &lt;a href="https://habr.com/ru/company/mailru/blog/470812/" target="_blank" rel="noopener noreferrer">Tarantool Cartridge&lt;/a>. It simplifies the whole app lifecycle – coding, testing, CI/CD, deployment, and support.&lt;/p>
&lt;p>This article shows Tarantool Cartridge in action - what it does for you with a special Ansible role:&lt;/p>
&lt;ul>
&lt;li>deploys your app to the cluster&lt;/li>
&lt;li>starts up all instances&lt;/li>
&lt;li>unites the instances into a cluster&lt;/li>
&lt;li>sets up authorisation&lt;/li>
&lt;li>bootstraps vshard (horizontal scaling)&lt;/li>
&lt;li>turns on automatic failover&lt;/li>
&lt;li>patches cluster configuration&lt;/li>
&lt;li>and keeps it all running smoothly!&lt;/li>
&lt;/ul>
&lt;p>See all of that with schemas and screenshots of the web-based GUI. Let’s dive right into it!&lt;/p>
&lt;h2 id="starting-off-with-a-sample">Starting off with a sample&lt;/h2>
&lt;p>Let us walk you through only some of the role’s functions. You can always find a full description of all its features and input parameters in the &lt;a href="https://github.com/tarantool/ansible-cartridge#ansible-role-tarantool-cartridge" target="_blank" rel="noopener noreferrer">documentation&lt;/a>. However, trying once is better than seeing it a hundred times, so let us deploy a small application.&lt;/p>
&lt;p>Tarantool Cartridge has a &lt;a href="https://github.com/tarantool/cartridge-cli/tree/master/examples/getting-started-app#application-example-based-on-tarantool-cartridge" target="_blank" rel="noopener noreferrer">tutorial&lt;/a> for creating a small Cartridge application that stores information about bank customers and their accounts, as well as provides an API for data management via HTTP. For this purpose, the application describes two possible roles that can be assigned to the instances: api and storage. Roles are Lua modules that implement some instance-specific functions and/or logic.&lt;/p>
&lt;p>Cartridge itself does not say anything about how to start processes — it only provides an opportunity to configure the running instances. So, the rest of it is up to the user: distributing configuration files, running services, and configuring topology. But we’re not going to do all of that — Ansible will do it for us.&lt;/p>
&lt;h3 id="getting-down-to-action">Getting down to action&lt;/h3>
&lt;p>First, let us deploy our application onto two virtual machines and set up a simple topology:&lt;/p>
&lt;ul>
&lt;li>The app-1 replica set will represent the api role that contains the vshard-router role. There will be just one instance.&lt;/li>
&lt;li>The storage-1 replica set will represent the storage role (including the vshard-storage role) — here we will add two instances from different machines.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure>&lt;img width="1560" height="780" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_ed783f2605bf9b93.webp 768w, https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_ce4899bccc840cf6.webp 480w, https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_3dce6fd43e7c6373.webp 1400w" src="https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_3dce6fd43e7c6373.webp" alt="Storage-1 replicaset" loading="lazy" />&lt;/figure> To run the sample, we will need &lt;a href="https://www.vagrantup.com/" target="_blank" rel="noopener noreferrer">Vagrant&lt;/a> and &lt;a href="https://www.ansible.com/" target="_blank" rel="noopener noreferrer">Ansible&lt;/a> (version 2.8 or higher).&lt;/p>
&lt;p>The role itself is stored in &lt;a href="https://galaxy.ansible.com/docs/" target="_blank" rel="noopener noreferrer">Ansible Galaxy&lt;/a> — a repository that allows you to share your work and use the ready-made roles.&lt;/p>
&lt;p>Now clone the sample repository:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ git clone https://github.com/dokshina/deploy-tarantool-cartridge-app.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd deploy-tarantool-cartridge-app &amp;&amp; git checkout 1.0.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then deploy the virtual machines:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ vagrant up&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After that, install the Tarantool Cartridge Ansible role:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-galaxy install tarantool.cartridge,1.1.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And start the installed role:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml playbook.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now wait until the playbook process is finished, go to &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and enjoy the results:&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2594" height="1338" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/02/tarantool-dashboard_hu_d58b5f57713537c8.webp 768w, https://percona.community/blog/2020/02/tarantool-dashboard_hu_a783770eb7dea209.webp 480w, https://percona.community/blog/2020/02/tarantool-dashboard_hu_daf8c226ce145b65.webp 1400w" src="https://percona.community/blog/2020/02/tarantool-dashboard_hu_daf8c226ce145b65.webp" alt="tarantool dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>You can upload the data now. Awesome, isn’t it?&lt;/p>
&lt;p>Now let’s figure out how to work with it, and we may as well add another replica set to the topology.&lt;/p>
&lt;h3 id="getting-deeper-into-details">Getting deeper into details&lt;/h3>
&lt;p>So, what happened?&lt;/p>
&lt;p>We got two virtual machines up and running and launched the Ansible playbook that configured our cluster. Now let’s look inside the playbook.yml file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- name: Deploy my Tarantool Cartridge app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: all
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> become: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> become_user: root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tasks:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - name: Import Tarantool Cartridge role
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> import_role:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: tarantool.cartridge&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Nothing interesting happens here; let’s launch the Ansible role called tarantool.cartridge.&lt;/p>
&lt;p>The most important things (namely, the cluster configuration) is in the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html" target="_blank" rel="noopener noreferrer">hosts.yml&lt;/a> inventory file:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # common cluster variables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_app_name: getting-started-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_package_path: ./getting-started-app-1.0.0-0.rpm # path to package
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cartridge_cluster_cookie: app-default-cookie # cluster cookie
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # common ssh options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_ssh_private_key_file: ~/.vagrant.d/insecure_private_key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_ssh_common_args: '-o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # INSTANCES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8181
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8182
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3302'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8183
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY MACHINES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # first machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the first machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # second machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the second machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY REPLICA SETS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_app_1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replica set configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: app-1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - app-1 # leader
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'api'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replica set instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replica set configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: storage-1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> weight: 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-1 # leader
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-1-replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'storage'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replica set instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>All we need to do is learn how to manage instances and replica sets by modifying this file. Later on, we will add new sections to it. In order to avoid confusion while adding the sections, look at the final version of this file, or &lt;code>hosts.updated.yml&lt;/code>, which is located in the sample repository.&lt;/p>
&lt;p>In Ansible terms, each instance is a host (not to be confused with a physical server), i.e. the infrastructure node that Ansible will manage. For each host, we can specify connection parameters (such as &lt;code>ansible_host&lt;/code> and &lt;code>ansible_user&lt;/code>) and instance configuration.&lt;/p>
&lt;p>The instance description is in the &lt;code>hosts &lt;/code>section. Let’s look into the configuration of the &lt;code>storage-1&lt;/code> instance:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # INSTANCES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3301'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8181
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In the &lt;code>config&lt;/code> variable, we specified the instance parameters: &lt;code>advertise URI&lt;/code> and &lt;code>HTTP port&lt;/code>.&lt;/p>
&lt;p>Below are the parameters of the &lt;code>app-1&lt;/code> and &lt;code>storage-1-replica&lt;/code> instances.&lt;/p>
&lt;p>We should provide Ansible with connection parameters for each instance. It seems reasonable to group the instances by virtual machines. For this purpose, the instances are grouped together under &lt;code>host1&lt;/code> and &lt;code>host2&lt;/code>, and each group in the &lt;code>vars&lt;/code> section contains the &lt;code>ansible_host&lt;/code> and &lt;code>ansible_user&lt;/code> parameter values for a single virtual machine. And the &lt;code>hosts&lt;/code> section contains hosts (or instances) included in this group:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY MACHINES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # first machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the first machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # second machine connection options
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_host: 172.19.0.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ansible_user: vagrant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the second machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s start editing &lt;code>hosts.yml&lt;/code>. Now we add two more instances: &lt;code>storage-2-replica&lt;/code> on the first virtual machine and &lt;code>storage-2&lt;/code> on the second one:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # INSTANCES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.3:3303'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8184
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> config:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> advertise_uri: '172.19.0.2:3302'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> http_port: 8185
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY MACHINES
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the first machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> host2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # instances to be started on the second machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app-1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-1-replica:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2: # &lt;==&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Start the Ansible playbook:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit storage-2,storage-2-replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> playbook.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note the &lt;code>--limit &lt;/code>option. Since each cluster instance is a host in terms of Ansible, we can explicitly specify which instances should be configured when running the playbook.&lt;/p>
&lt;p>So we go back to the web UI at &lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer">http://localhost:8181/admin/cluster/dashboard&lt;/a> and look at our new instances:
&lt;figure>&lt;img width="2614" height="1526" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/02/tarantool-new-instances_hu_f34a726eaab2576f.webp 768w, https://percona.community/blog/2020/02/tarantool-new-instances_hu_b4df25e3e8cb7dc1.webp 480w, https://percona.community/blog/2020/02/tarantool-new-instances_hu_50cb372c36495300.webp 1400w" src="https://percona.community/blog/2020/02/tarantool-new-instances_hu_50cb372c36495300.webp" alt="Tarantool new instances in dashboard" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Next, let’s master topology management.&lt;/p>
&lt;h3 id="managing-the-topology">Managing the topology&lt;/h3>
&lt;p>Let us group our new instances into the storage-2 replica set, add a new group of replicaset_storage_2, and describe the replica set parameters in the variables as we did for replicaset_storage_1. In the hosts section, we specify which instances should be included in this group (i.e. our replica set):&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">all:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> children:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GROUP INSTANCES BY REPLICA SETS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_storage_2: # &lt;==
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vars: # replicaset configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> replicaset_alias: storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> weight: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> failover_priority:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - storage-2-replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> roles:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 'storage'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hosts: # replicaset instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> storage-2-replica:&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Then we run the playbook again:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ ansible-playbook -i hosts.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --limit replicaset_storage_2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --tags cartridge-replicasets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> playbook.yml&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This time we pass the name of the group corresponding to our replica set in the &lt;code>--limit&lt;/code> parameter.&lt;/p>
&lt;p>Let’s look at the &lt;code>tags&lt;/code> option.&lt;/p>
&lt;p>Our role successively executes various tasks marked with the following tags:&lt;/p>
&lt;ul>
&lt;li>&lt;code>cartridge-instances&lt;/code>: instance management (configuration, membership);&lt;/li>
&lt;li>&lt;code>cartridge-replicasets&lt;/code>: topology management (replica set management and permanent removal (expel) of instances from the cluster);&lt;/li>
&lt;li>&lt;code>cartridge-config&lt;/code>: control of other cluster parameters (vshard bootstrapping, automatic failover, authorization parameters, and application configuration).&lt;/li>
&lt;/ul>
&lt;p>We can explicitly specify what part of the work we want to be done — and the role will skip the rest of the tasks. In this case, we only want to work with topology, so we specify &lt;code>cartridge-replicasets&lt;/code>.&lt;/p>
&lt;p>Let us evaluate the result of our efforts. Find the new replica set at&lt;a href="http://localhost:8181/admin/cluster/dashboard" target="_blank" rel="noopener noreferrer"> http://localhost:8181/admin/cluster/dashboard&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2612" height="1562" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_62db01e98a6bb2c2.webp 768w, https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_3a28f8a8ce4caff0.webp 480w, https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_f39a845f251e99d7.webp 1400w" src="https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_f39a845f251e99d7.webp" alt="Tarantool new replicaset" loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Yay!&lt;/p>
&lt;p>Try changing the configuration of the instances and replica sets and see how the topology of the cluster changes. You can try different use cases, such as &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#rolling-update-batch-size" target="_blank" rel="noopener noreferrer">rolling update&lt;/a> or &lt;code>memtx_memory&lt;/code> increase. The role would try to do this without restarting the instance to reduce the possible downtime of your application.&lt;/p>
&lt;p>Don’t forget to run &lt;code>vagrant halt&lt;/code> to stop the virtual machines when you’re done with them.&lt;/p>
&lt;h2 id="whats-inside">What’s inside?&lt;/h2>
&lt;p>Here I will tell you more about what happened under the hood of the Ansible role during our tests.&lt;/p>
&lt;p>Let’s consider the steps of deploying a Cartridge application.&lt;/p>
&lt;h3 id="installing-the-package-and-starting-the-instances">Installing the package and starting the instances&lt;/h3>
&lt;p>The first thing to do is to deliver the package to the server and install it. Now the role can work with RPM-packages and DEB-packages.&lt;/p>
&lt;p>Next, we launch the instances. It is very simple: every instance is a separate &lt;code>systemd&lt;/code> service. For example:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-12">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ systemctl start myapp@storage-1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This command launches the &lt;code>storage-1&lt;/code> instance of the &lt;code>myapp&lt;/code> application. The running instance looks for its &lt;a href="https://www.tarantool.io/ru/doc/2.2/book/cartridge/cartridge_dev/#configuring-instances" target="_blank" rel="noopener noreferrer">configuration&lt;/a> in &lt;code>/etc/tarantool/conf.d/&lt;/code>. You can view the instance logs using &lt;code>journald&lt;/code>.&lt;/p>
&lt;p>The Unit file &lt;code>/etc/systemd/systemd/myapp@.sevice&lt;/code> for the systemd service is delivered with the package.&lt;/p>
&lt;p>Ansible has built-in modules for installing packages and managing systemd services, so we did not invent anything new here.&lt;/p>
&lt;h3 id="configuring-the-cluster-topology">Configuring the cluster topology&lt;/h3>
&lt;p>The most exciting things happen here. I am sure you would agree that it is strange to bother with a special Ansible role for installing packages and running &lt;code>systemd&lt;/code> services.&lt;/p>
&lt;p>You can configure the cluster manually:&lt;/p>
&lt;ul>
&lt;li>The first option is to open the Web UI and click on the buttons. It is quite suitable for a one-time start of several instances.&lt;/li>
&lt;li>The second option is to use GraphQL API. Here you can already automate something, for example, write a script in Python.&lt;/li>
&lt;li>The third option is for the courageous: go to the server, connect to one of the instances with the help of &lt;code>tarantoolctl connect&lt;/code> and perform all the necessary actions with the &lt;code>cartridge &lt;/code>Lua module.&lt;/li>
&lt;/ul>
&lt;p>The main task of our invention is to do this most difficult part of the work for you.&lt;/p>
&lt;p>Ansible allows you to write your own module and use it in your role. Our role uses these modules to manage the various cluster components.&lt;/p>
&lt;p>How does it work? You describe the desired state of the cluster in a declarative configuration, and the role gives each module its own configuration section as input. The module receives the current state of the cluster and compares it with the input. Then the code for the necessary cluster state is launched using the socket of one of the instances.&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>Today we have shown you how to deploy your Tarantool Cartridge application and configure a simple topology. To do this, we used Ansible, a powerful tool that is easy to use and allows you to configure multiple infrastructure nodes at the same time (in our case, the cluster instances).&lt;/p>
&lt;p>Above we went over one of the many ways to describe the cluster configuration by means of Ansible. Once you feel that you are ready for more, learn the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html" target="_blank" rel="noopener noreferrer">best practices&lt;/a> for writing playbooks. You may find it easier to manage the topology with &lt;code>group_vars&lt;/code> and &lt;code>host_vars&lt;/code>.&lt;/p>
&lt;p>Very soon, we will tell you how to remove (expel) instances from the topology permanently, bootstrap vshard, manage automatic failover, configure authorization, and patch cluster configuration. In the meantime, you can review the &lt;a href="https://github.com/tarantool/ansible-cartridge#ansible-role-tarantool-cartridge" target="_blank" rel="noopener noreferrer">documentation&lt;/a> yourself and try changing cluster settings.&lt;/p>
&lt;p>If something goes wrong, make sure to &lt;a href="https://github.com/tarantool/ansible-cartridge/issues/new" target="_blank" rel="noopener noreferrer">let us know&lt;/a> about the problem. We will do our best to resolve any issue!&lt;/p></content:encoded><author>Elizaveta Dokshina</author><category>Open Source Databases</category><category>Tarantool</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/02/tarantool-new-instances-scaled_hu_7463580befc6a111.jpg"/><media:content url="https://percona.community/blog/2020/02/tarantool-new-instances-scaled_hu_931a06ec2aac745f.jpg" medium="image"/></item><item><title>Finding MySQL Scaling Problems Using perf</title><link>https://percona.community/blog/2020/02/05/finding-mysql-scaling-problems-using-perf/</link><guid>https://percona.community/blog/2020/02/05/finding-mysql-scaling-problems-using-perf/</guid><pubDate>Wed, 05 Feb 2020 16:18:14 UTC</pubDate><description>The thing I wish I’d learned while still a DBA is how to use perf. Conversely after moving to a developer role, getting access to real external client workloads to get a perf recording directly is rare. To bridge this gap, I hope to encourage a bit of perf usage to help DBAs report bugs/feature requests in more detail to MySQL developers, who can then serve your needs better.</description><content:encoded>&lt;p>The thing I wish I’d learned while still a DBA is how to use &lt;a href="https://perf.wiki.kernel.org/index.php/Main_Page" target="_blank" rel="noopener noreferrer">perf&lt;/a>. Conversely after moving to a developer role, getting access to real external client workloads to get a perf recording directly is rare. To bridge this gap, I hope to encourage a bit of perf usage to help DBAs report bugs/feature requests in more detail to MySQL developers, who can then serve your needs better.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="960" height="474" sizes="(max-width: 960px) 100vw, 960px" srcset="https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_f1929b790bf6be07.webp 768w, https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_631f3888e1185e5b.webp 480w, https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_a0aa5d556bee01c.webp 960w" src="https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_a0aa5d556bee01c.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>A recent client request showed how useful perf is in exposing the areas of MySQL that are otherwise well tuned, but can still be in need of coding improvements that increase throughput. The client had a &lt;a href="https://sourceforge.net/projects/tpccruner/" target="_blank" rel="noopener noreferrer">TPCCRunner&lt;/a> (variant) workload that they wanted to run on a &lt;a href="https://www.ibm.com/it-infrastructure/power/power9" target="_blank" rel="noopener noreferrer">Power 9&lt;/a> CPU, in &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html#isolevel_read-committed" target="_blank" rel="noopener noreferrer">READ-COMMITTED&lt;/a> mode, and they received less performance than they hoped. Being a 2 socket, 20 cpus/socket 4 threads per core, and 256G RAM total it has enough resources.&lt;/p>
&lt;p>With such abundance of resources, the perf profile exposed code bottlenecks not normally seen.&lt;/p>
&lt;p>The principles driving MySQL development for a considerable time have been to a) maintain correctness, and b) deliver performance, usually meaning the CPU should be the bottleneck. The whole reason for large innodb buffer pools, innodb MVCC / LSN, group commit, table caches, thread caches, indexes, query planner etc, is to ensure that all hot data is in memory, ready to be processed optimally in the most efficient way by the CPU.&lt;/p>
&lt;p>Based on this principle, without a requirement to sync to persistent storage for durability, a SQL read mostly load should be able to add scale linearly up to the CPU capacity. Ideally after the CPU capacity has been reached the throughput should stay at the capacity limit and not degrade. Practical overheads of thread measurement mean this is never perfectly achieved. However, it is the goal.&lt;/p>
&lt;h2 id="steps-to-using-perf">Steps to using perf&lt;/h2>
&lt;p>To install and use perf, use the following steps:&lt;/p>
&lt;h4 id="1-install-perf">1. Install perf&lt;/h4>
&lt;p>This is a standard package and is closely tied to the Linux kernel version. The package name varies per distro:&lt;/p>
&lt;ul>
&lt;li>Ubuntu: &lt;a href="https://packages.ubuntu.com/bionic/linux-tools-common" target="_blank" rel="noopener noreferrer">linux-tools-common&lt;/a>&lt;/li>
&lt;li>Debian: &lt;a href="https://packages.debian.org/buster/linux-base" target="_blank" rel="noopener noreferrer">linux-base&lt;/a>&lt;/li>
&lt;li>RHEL / Centos / Fedora: perf&lt;/li>
&lt;/ul>
&lt;p>Distributions normally set the &lt;a href="http://man7.org/linux/man-pages/man8/sysctl.8.html" target="_blank" rel="noopener noreferrer">sysctl&lt;/a> &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#perf-event-paranoid" target="_blank" rel="noopener noreferrer">&lt;em>kernel.perf_event_paranoid&lt;/em>&lt;/a> to a level which is hard to use (or &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html" target="_blank" rel="noopener noreferrer">exploit&lt;/a>) and this may need to be adjusted to obtain our recording. Large perf recordings due to hardware threads can require file descriptors and memory, and their limits may need to be increased with care (see &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html#perf-events-perf-resource-control" target="_blank" rel="noopener noreferrer">kernel manual&lt;/a>).&lt;/p>
&lt;h4 id="2-install-debug-symbols-aka-debug-info-for-mysql">2. Install debug symbols (a.k.a. debug info) for MySQL&lt;/h4>
&lt;p>Debug symbols mapping memory addresses to real server code can assist greatly in understanding the recorded results. The debug info needs to map to the exact build of MySQL (both version number and its origin).&lt;/p>
&lt;p>Distros provide debug information in separate package repositories (distribution instructions: &lt;a href="https://wiki.ubuntu.com/Debug%20Symbol%20Packages" target="_blank" rel="noopener noreferrer">Ubuntu&lt;/a>, &lt;a href="https://wiki.debian.org/AutomaticDebugPackages" target="_blank" rel="noopener noreferrer">Debian&lt;/a>, &lt;a href="https://access.redhat.com/solutions/9907" target="_blank" rel="noopener noreferrer">RHEL&lt;/a>, &lt;a href="https://fedoraproject.org/wiki/StackTraces#What_are_debuginfo_rpms.2C_and_how_do_I_get_them.3F" target="_blank" rel="noopener noreferrer">Fedora&lt;/a>) and MySQL, &lt;a href="https://mariadb.com/kb/en/library/how-to-produce-a-full-stack-trace-for-mysqld/#installing-debug-info-packages-on-linux" target="_blank" rel="noopener noreferrer">MariaDB&lt;/a> and Percona provide debug info packages in their repositories without additional configuration.&lt;/p>
&lt;p>If compiling from source, the default cmake option -DCMAKE_BUILD_TYPE=RelWithDebugInfo  has debug info as the name suggests.&lt;/p>
&lt;h4 id="3-ensure-that-your-table-structures-and-queries-are-sane">3. Ensure that your table structures and queries are sane.&lt;/h4>
&lt;p>MySQL works well when the database table structures, indexes, and queries are in a &lt;code>natural&lt;/code> simple form. Asking MySQL developers to make poor table structures/queries to achieve greater performance will attract a low priority as making these changes can add to the overhead of simple queries.&lt;/p>
&lt;h4 id="4-ensure-that-you-have-tuned-the-database-for-the-workload">4. Ensure that you have tuned the database for the workload.&lt;/h4>
&lt;p>MySQL has a lot of system variables, and using the performance schema and status variables assists in creating an optimally tuned MySQL instance before beginning perf measurements.&lt;/p>
&lt;h4 id="5-ensure-that-the-active-data-is-off-disk">5. Ensure that the active data is off disk&lt;/h4>
&lt;p>To ensure you measurement is at its maximum, having the hot part of the data loaded into memory enables perf to focus on recording CPU related areas under stress, not just waiting to load from disk.&lt;/p>
&lt;p>For example, the TPCCRunner example described earlier took about an hour before it reached a point where it achieved its maximum transaction throughput. TPCRunner displays this, but generally watch for a leveling out of the queries per second over several minutes.&lt;/p>
&lt;p>When starting/stopping mysqld for testing, &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_buffer_pool_dump_at_shutdown" target="_blank" rel="noopener noreferrer">innodb_buffer_pool_dump_at_shutdown&lt;/a>=1 / innodb_buffer_pool_dump_at_start=1 / innodb_buffer_pool_dump_pct=100 will help restore the innodb buffer pool significantly quicker.&lt;/p>
&lt;h4 id="6-know-what-workload-is-being-measured">6. Know what workload is being measured&lt;/h4>
&lt;p>A batch job may not have the same throughput requirements. It also may impact the concurrent workload that you are perf recording by creating longer history length, innodb buffer pool pressure etc.&lt;/p>
&lt;p>The application that generates the workload should be on a different server, different VM or in some way constrained in CPU to avoid resource contention with mysqld. Check the client side to ensure that it isn’t overloaded (CPU, network) as this could be indirectly constraining the server side workload.&lt;/p>
&lt;h2 id="measuring">Measuring&lt;/h2>
&lt;p>With a hot workload running let’s start some measurement.&lt;/p>
&lt;p>Perf uses hardware (PMU) to assist its recording work, but there are limits to hardware support so there’s a point where it will affect your workload, so start slow. Perf works by looking at a frequency distribution of where the mysqld process is spending its time. To examine a function that is taking 0.1% of the time means that 1000 samples will likely show it once. As such a few thousand samples is sufficient. The number of samples is the multiplication of &lt;a href="http://man7.org/linux/man-pages/man1/perf-record.1.html" target="_blank" rel="noopener noreferrer">perf record’s&lt;/a> &lt;em>-F / –freq&lt;/em> – which may by default be several thousand / second – the recording duration, and the number of CPUs.&lt;/p>
&lt;p>If your SQL queries are all running in much less than a second and occurring frequently, then a high frequency recording for a short duration is sufficient. If some query occurs less often, with a high CPU usage spike, a helper program &lt;a href="https://github.com/Netflix/flamescope" target="_blank" rel="noopener noreferrer">FlameScope&lt;/a> will be able to narrow down a perf recording to a usable sample interval.&lt;/p>
&lt;p>Analysis involves looking through a number of sets of data. Below I show a pattern of using _name _as a shell variable, and a large one line command to conduct a number of recordings in sequence. In my case, I cycled through _RC _ (read-committed) vs _RR _(repeatable read), different compile options &lt;em>-O0&lt;/em> , kernel versions, final stages of _warmup _(compared to test run) and even local changes to mysqld (thread_local_ut_rnd_ulint_counter ). Keeping track of these alongside the same measurement of the test run output helps to correlate results more easily.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">name=5.7.28-thread_local_ut_rnd_ulint_counterO0-RC_warmup2 ;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pid=$(pidof mysqld);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">perf record -F 10 -o mysql-${name}.perf -p $pid  -- sleep 20;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">perf record -g -F 10 -o mysql-${name}.g.perf -p $pid  -- sleep 5;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">perf stat -B -e cache-references,cache-misses,cycles,instructions,branches,faults,migrations -p $pid sleep 20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2>&amp;1 | tee perf-stats-${name}.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>With the above command, the recording is constrained the recording to mysqld (&lt;em>-p $pid&lt;/em>), at &lt;em>-F 10&lt;/em> samples/per second for (&lt;em>sleep&lt;/em>) &lt;em>20&lt;/em> seconds. A longer recording without the stack trace (-g) is taken as reference point to see if the shorter recording with &lt;em>-g&lt;/em> stack trace is a fair sample. 10 hz x 20 seconds may not seem like many samples, however this occurred on each of the 160 threads. A record with &lt;em>-g&lt;/em> is needed as a perf profile that shows all time in the kernel or pthread mutex (lock) code, but it doesn’t mean much without knowing which lock it is and where it was accessed from.&lt;/p>
&lt;p>Perf record with &lt;em>-g&lt;/em> call-graph (also known as stack chain or backtrace) adds to the size of the recording and the overhead of measurements. To ensure that there isn’t too much perf data (resulting in workload stalls), get the right frequency and duration before enabling &lt;em>-g&lt;/em>.&lt;/p>
&lt;p>Perf stats were measured to identify (cpu) cache efficiency, instructions/cycle efficiency, instructions throughput (watch out for frequency scaling), faults (connecting real memory to the virtual address - should be low after warmup), and migrations between numa nodes.&lt;/p>
&lt;p>During measurement look at &lt;em>htop&lt;/em>/&lt;em>top&lt;/em> to ensure that the CPUs are indeed loaded. Also check the client side isn’t flooded with connection errors that could impact the validity of the recorded results.&lt;/p>
&lt;h2 id="analysis">Analysis&lt;/h2>
&lt;h3 id="viewing-a-perf-recording">Viewing a perf recording&lt;/h3>
&lt;p>&lt;a href="http://man7.org/linux/man-pages/man1/perf-report.1.html" target="_blank" rel="noopener noreferrer">perf report&lt;/a> is used to textually view a perf recording. It is during the report stage that the debug info is read, since the linux kernel image resolves symbols. Run the report under &lt;code>nice -n 19 perf report&lt;/code> to ensure it has the lowest CPU priority if you are at all concerned about production impacts. It’s quite possible to do this on a different server provided the same kernel and MySQL packages are installed. &lt;code>perf report --input mysql-5.7.28-event_run2_warmup_run1.perf --stdio&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Total Lost Samples: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Samples: 91K of event 'cycles:ppp'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Event count (approx.): 1261395960159641
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Overhead Command Shared Object Symbol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># ........ ....... ................... ...............................................................................
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 5.84% mysqld mysqld [.] rec_get_offsets_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3.62% mysqld mysqld [.] MYSQLparse
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.70% mysqld mysqld [.] page_cur_search_with_match
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.70% mysqld mysqld [.] buf_page_get_gen
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.22% mysqld mysqld [.] cmp_dtuple_rec_with_match_low
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.93% mysqld mysqld [.] buf_page_hash_get_low
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.49% mysqld mysqld [.] btr_cur_search_to_nth_level
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.35% mysqld [kernel.kallsyms] [k] do_syscall_64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.14% mysqld mysqld [.] row_search_mvcc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.93% mysqld mysqld [.] alloc_root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.92% mysqld mysqld [.] lex_one_token
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.67% mysqld libc-2.27.so [.] malloc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.64% mysqld libc-2.27.so [.] _int_malloc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.61% mysqld libpthread-2.27.so [.] __pthread_getspecific
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.59% mysqld mysqld [.] pfs_rw_lock_s_lock_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.59% mysqld mysqld [.] dispatch_command
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.50% mysqld mysqld [.] check_stack_overrun
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.50% mysqld [tg3] [k] tg3_poll_work&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This shows a %CPU time measured when the CPU instruction pointer was at a particular time, grouped by the function name. To find out why malloc or the kernel do_syscall_64 appears so often the stack recording is needed.&lt;/p>
&lt;h3 id="viewing-a-perf-recording-with-a-stack">Viewing a perf recording with a stack&lt;/h3>
&lt;p>When the &lt;em>perf record&lt;/em> used &lt;em>-g&lt;/em>, then &lt;em>-g&lt;/em> can be used in perf report to show the breakdown. By default it groups the functions, including the functions it calls, as below.
&lt;code>perf report -i mysql-5.7.28-event_run2_warmup_run1.g.perf&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Samples: 85K of event 'cycles:ppp', Event count (approx.): 261413311777846
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Children Self Command Shared Object Symbol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 80.08% 0.00% mysqld libpthread-2.27.so [.] start_thread
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 80.08% 0.00% mysqld mysqld [.] pfs_spawn_thread
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 80.05% 0.07% mysqld mysqld [.] handle_connection
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 79.75% 0.14% mysqld mysqld [.] do_command
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 77.98% 0.70% mysqld mysqld [.] dispatch_command
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 75.32% 0.18% mysqld mysqld [.] mysql_parse
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 62.63% 0.38% mysqld mysqld [.] mysql_execute_command
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 58.65% 0.13% mysqld mysqld [.] execute_sqlcom_select
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 55.63% 0.05% mysqld mysqld [.] handle_query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 25.31% 0.41% mysqld mysqld [.] st_select_lex::optimize
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 24.67% 0.12% mysqld mysqld [.] JOIN::exec
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 24.45% 0.59% mysqld mysqld [.] JOIN::optimize
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 22.62% 0.29% mysqld mysqld [.] sub_select
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ 20.15% 1.56% mysqld mysqld [.] btr_cur_search_to_nth_level&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>In MySQL, as expected, most significant CPU load is in the threads. Most of the time this is a user connection, under the &lt;em>handle_connection&lt;/em> function, which parses and executes the SQL. In different situations you might see innodb background threads, or replication threads: understanding which thread is causing the load is important at a top level. Then, to continue analysis, use the perf report &lt;em>–no-children&lt;/em> option. This will show approximately the same as the non_-g_ recording, however it will provide the mechanism of being able to hit Enter on a function to show all the call stacks that go to that particular function.
&lt;code>perf report -g --no-children --input mysql-5.7.28-event_run2_warmup_run1.g.perf&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> Overhead Command Shared Object Symbol ◆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 6.24% mysqld mysqld [.] rec_get_offsets_func ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start_thread ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pfs_spawn_thread ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> handle_connection ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dispatch_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql_parse ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql_execute_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execute_sqlcom_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - handle_query ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 4.57% JOIN::exec ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - sub_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 3.77% evaluate_join_record ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 0.60% join_read_always_key ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 1.67% st_select_lex::optimize&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This shows a common call stack into &lt;em>handle_query&lt;/em>, where the &lt;em>JOIN::exec&lt;/em> and &lt;em>st_select_lex::optimize&lt;/em> is the diverging point. If the &lt;em>evaluate_join_record&lt;/em> and other sub-functions were to be expanded, the bottom level of the call graph would show &lt;em>rec_get_offsets_func.&lt;/em>&lt;/p>
&lt;h3 id="disassembly-annotation">Disassembly (annotation)&lt;/h3>
&lt;p>In the ncurses interfaces. Selecting ‘a’ (annotate) on a particular function calls out to the &lt;a href="https://linux.die.net/man/1/objdump" target="_blank" rel="noopener noreferrer">objdump&lt;/a> (binutils) disassembler to show where in this function the highest frequency occurred and maps this to a commented C++ code above it.&lt;/p>
&lt;p>As compilers have significant understanding of the architecture, and given that the C/C++ language provides significant freedom in generating code, it’s sometimes quite difficult to parse from assembly back to the C/C++ source. In complex operations, C++ variables don’t have an easy translation to CPU registers. Inlined functions are also particularly hard as each inlining can further be optimized to a unique assembly depending on its location. To understand the assembly, I recommend focusing on the loads, stores, maths/conditions with constants and branches to see which register maps to which part of the MySQL server code in the context of the surrounding code.&lt;/p>
&lt;p>E.g annotation on &lt;em>rec_get_offsets_func&lt;/em>:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> │ dict_table_is_comp():
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ #if DICT_TF_COMPACT != 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ #error "DICT_TF_COMPACT must be 1"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ #endif
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ return(table->flags &amp; DICT_TF_COMPACT);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.44 │ mov 0x20(%rsi),%rsi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 39.30 │ movzbl -0x3(%rdi),%eax
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ _Z20rec_get_offsets_funcPKhPK12dict_index_tPmmPP16mem_block_info_t():
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_ad(rec);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_ad(index);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_ad(heap);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ if (dict_table_is_comp(index->table)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.44 │ testb $0x1,0x34(%rsi)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.15 │ ↓ je e611d8 &lt;rec_get_offsets_func(unsigned char const*, dict_index_t const*, 128
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ switch (UNIV_EXPECT(rec_get_status(rec),&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here we see that &lt;em>dict_table_is_comp&lt;/em> is an expanded inline function at the top of &lt;em>rec_get_offsets&lt;/em>, the &lt;em>movzlb .. %eax.&lt;/em> The dominate CPU use in the function however isn’t part of this. The &lt;em>testb 0x1 (DICT_TF_COMPACT) … %rsi&lt;/em> is the testing of the flag with &lt;em>je&lt;/em> afterwards to return from the function.&lt;/p>
&lt;h2 id="example---mutex-contention">Example - mutex contention&lt;/h2>
&lt;p>Compared to the performance profile on x86 above under ‘Viewing a perf recording’, this is what the performance profile looked like on POWER. perf report –input mysql-5.7.28-read_mostly_EVENT_RC-run2.perf –stdio&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Total Lost Samples: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Samples: 414K of event 'cycles:ppp'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Event count (approx.): 3884039315643070
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Overhead Command Shared Object Symbol
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># ........ ....... ................... ...............................................................................
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 13.05% mysqld mysqld [.] MVCC::view_open
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10.99% mysqld mysqld [.] PolicyMutex&lt;TTASEventMutex&lt;GenericPolicy> >::enter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 4.11% mysqld mysqld [.] rec_get_offsets_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3.78% mysqld mysqld [.] buf_page_get_gen
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.34% mysqld mysqld [.] MYSQLparse
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.27% mysqld mysqld [.] cmp_dtuple_rec_with_match_low
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.15% mysqld mysqld [.] btr_cur_search_to_nth_level
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2.05% mysqld mysqld [.] page_cur_search_with_match
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.99% mysqld mysqld [.] ut_delay
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.83% mysqld mysqld [.] mtr_t::release_block_at_savepoint
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.35% mysqld mysqld [.] rw_lock_s_lock_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.96% mysqld mysqld [.] buf_page_hash_get_low
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.88% mysqld mysqld [.] row_search_mvcc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.84% mysqld mysqld [.] lex_one_token
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.80% mysqld mysqld [.] pfs_rw_lock_s_unlock_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.80% mysqld mysqld [.] mtr_t::commit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.62% mysqld mysqld [.] pfs_rw_lock_s_lock_func
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.59% mysqld [kernel.kallsyms] [k] power_pmu_enable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.59% mysqld [kernel.kallsyms] [k] _raw_spin_lock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.55% mysqld libpthread-2.28.so [.] __pthread_mutex_lock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.54% mysqld mysqld [.] alloc_root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.43% mysqld mysqld [.] PolicyMutex&lt;TTASEventMutex&lt;GenericPolicy> >::exit&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What stands out clearly is the top two entries that didn’t appear on x86. Looking closer at &lt;em>MVCC::view_open&lt;/em>:
&lt;code>perf report -g --no-children --input mysql-5.7.28-read_mostly_EVENT_RC-run2.g.perf&lt;/code>&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">- 13.47% mysqld mysqld [.] MVCC::view_open ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> __clone ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0x8b10 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pfs_spawn_thread ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> handle_connection ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dispatch_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql_parse ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mysql_execute_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execute_sqlcom_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - handle_query ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 11.22% JOIN::exec ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - sub_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 10.99% join_read_always_key ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> handler::ha_index_read_map ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ha_innobase::index_read ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - row_search_mvcc ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 10.99% trx_assign_read_view ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MVCC::view_open&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Annotation of MVCC::view_open&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> │ _ZNK14TTASEventMutexI13GenericPolicyE7is_freeEjjRj(): ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ bool is_free( ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │a08: ↓ bne cr4,10db7a30 &lt;MVCC::view_open(ReadView*&amp;, a90 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ↓ b 10db7ad0 &lt;MVCC::view_open(ReadView*&amp;, trx_t*)+0xb30> ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_rnd_gen_ulint(): ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_rnd_ulint_counter = UT_RND1 * ut_rnd_ulint_counter + UT_RND2; ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │a10: addis r7,r2,2 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │ addi r7,r7,26904 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 81.15 │ ld r8,0(r7) ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │ mulld r8,r27,r8 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │ addis r8,r8,1828 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │ addi r8,r8,-14435 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_rnd_gen_next_ulint(): ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ rnd = UT_RND2 * rnd + UT_SUM_RND3; ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.02 │ mulld r9,r8,r19 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_rnd_gen_ulint(): ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ut_rnd_ulint_counter = UT_RND1 * ut_rnd_ulint_counter + UT_RND2; ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.04 │ std r8,0(r7)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Due to the inline of code, within &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/read/read0read.cc#L554..L611" target="_blank" rel="noopener noreferrer">MVCC::view_open&lt;/a> one of the mutexs got expanded out and the random number is used to spinlock wait for the lock again. &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/include/ib0mutex.h#L707..L717" target="_blank" rel="noopener noreferrer">PolicyMutex&lt;TTASEventMutex&lt;GenericPolicy> >::enter&lt;/a> expanded to exactly the same code.&lt;/p>
&lt;p>We see here that the load (&lt;em>ld&lt;/em>) into &lt;em>r8&lt;/em>, is the slowest part of this. In mysql-5.7.28, &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/ut/ut0rnd.cc#L48" target="_blank" rel="noopener noreferrer">ut_rnd_ulint_counter&lt;/a> is an ordinary global variable, meaning its shared between threads. The simple line of code &lt;em>ut_rnd_ulint_counter = UT_RND1 * ut_rnd_ulint_counter + UT_RND2&lt;/em>,  shows the result stored back in the same variable. To understand why this didn’t scale, we need to understand cache lines.&lt;/p>
&lt;p>note: &lt;em>MVCC::view_open&lt;/em> did show up in the x86 profile, at 0.23% and had the lock release as the highest cpu point. For x86 &lt;em>PolicyMutex&lt;TTASEventMutex&lt;GenericPolicy> >::enter&lt;/em> was at 0.32%.&lt;/p>
&lt;h3 id="cache-lines">Cache Lines&lt;/h3>
&lt;p>All modern CPUs that are likely to support MySQL will have some from of &lt;a href="https://en.wikipedia.org/wiki/Cache_hierarchy" target="_blank" rel="noopener noreferrer">cache hierarchy&lt;/a>. The principles are that a largely accessed memory location, like &lt;em>ut_rnd_ulint_counter&lt;/em>, can be copied into cache and at some point the CPU will push it back to memory. To ensure behavior is consistent, cache lines represent a MMU (memory management unit) concept of a memory allocation to a particular CPU. Cache lines can be read only, or exclusive, and a protocol between CPU cores exists to ensure that exclusive access is to one CPU only. When one CPU modifies a memory location it gains an exclusive cache line, and the cached value in other CPU caches are flushed. At which cache level this flushing occurs at, and to what extent are caches shared between CPUs, is quite architecture specific. However, citing rough &lt;a href="http://brenocon.com/dean_perf.html" target="_blank" rel="noopener noreferrer">metrics&lt;/a>, cache access is orders of magnitude faster than RAM.&lt;/p>
&lt;p>In the perf recording above, storing back of &lt;em>ut_rnd_ulint_counter&lt;/em> clears the cache for the other CPUs, and this is why the load instruction is slow. MySQL did have this fixed in &lt;a href="https://github.com/mysql/mysql-server/commit/dedc8b3d567fbb92ce912f1559fe6a08b2857045" target="_blank" rel="noopener noreferrer">5.7.14&lt;/a> but reverted this fix in &lt;a href="https://github.com/mysql/mysql-server/commit/dedc8b3d567fbb92ce912f1559fe6a08b2857045" target="_blank" rel="noopener noreferrer">5.7.20&lt;/a> (assuming some performance degradation in thread local storage). In MySQL &lt;a href="https://github.com/mysql/mysql-server/commit/ea4913b403db72f26565520f68686b385872e7d2#diff-5f582f65ca6be1efafb5e278e4bffc44R35" target="_blank" rel="noopener noreferrer">8.0+&lt;/a>, &lt;em>ut_rnd_ulint_counter&lt;/em> is a C++11 &lt;a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2659.htm" target="_blank" rel="noopener noreferrer">thread_local&lt;/a> variable which has a faster implementation. &lt;a href="https://github.com/MariaDB/server/commit/ce04790" target="_blank" rel="noopener noreferrer">MariaDB-10.3.5&lt;/a> avoided this by removing the random delay in InnoDB mutexes. Thread local variables reduce contention because each thread has its independent memory location. Because this is only a random number seed, there’s no need for synchronization of results.&lt;/p>
&lt;h3 id="cache-collisions">Cache collisions&lt;/h3>
&lt;p>The impacts of &lt;em>ut_rnd_ulint_counter&lt;/em> however aren’t limited to itself. Cache lines reserve blocks of memory according to the cache line size of the architecture (x86 - 64 bytes, arm64 and POWER - 128 bytes, s390 - 256 bytes). High in the CPU profile is the &lt;em>btr_cur_search_to_nth_level&lt;/em> function. This is part of innodb’s scanning of an index and it would be easy to discount its high CPU usage. Looking at the disassembly however shows:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-8">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> 0.01 │ ld r8,3464(r31)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ cursor->low_match = low_match;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.05 │ std r10,96(r25)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ cursor->up_bytes = up_bytes;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.00 │ ld r10,3456(r31)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ if (btr_search_enabled &amp;&amp; !index->disable_ahi) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 24.08 │ lbz r9,0(r9)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ cursor->low_bytes = low_bytes;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.01 │ std r7,104(r25)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ cursor->up_match = up_match;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.00 │ std r8,80(r25)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ cursor->up_bytes = up_bytes;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.01 │ std r10,88(r25)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ if (UNIV_LIKELY(btr_search_enabled) &amp;&amp; !index->disable_ahi) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.00 │ cmpwi cr7,r9,0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.00 │ ld r9,48(r29)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 0.01 │ ↓ beq cr7,10eb88a8 &lt;btr_cur_search_to_nth_level(dict_index_t*, 2348&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;em>lbz&lt;/em> is a load byte instruction referring to &lt;em>btr_search_enabled&lt;/em>. &lt;em>btr_search_enabled&lt;/em> and is the MySQL server variable associated with the SQL global &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_adaptive_hash_index" target="_blank" rel="noopener noreferrer">innodb_adaptive_hash_index&lt;/a> . As a global system variable, this isn’t changed frequently, probably only once at startup. As such it should be able to rest comfortably in the cache of all CPUs in a read only cache line.&lt;/p>
&lt;p>To find out why the relative address is examined in the mysql executable:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-9">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ readelf -a bin/mysqld | grep btr_search_enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 8522: 0000000011aa1b40 1 OBJECT GLOBAL DEFAULT 24 btr_search_enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 17719: 0000000011aa1b40 1 OBJECT GLOBAL DEFAULT 24 btr_search_enabled&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Taking the last two characters off the hexadecimal address &lt;em>0000000011aa1b40&lt;/em> and the other variables in the same 256 (0x100) byte address range can be examined.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-10">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">$ readelf -a bin/mysqld | grep 0000000011aa1b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1312: 0000000011aa1be0 296 OBJECT GLOBAL DEFAULT 24 fts_default_stopword
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 8522: 0000000011aa1b40 1 OBJECT GLOBAL DEFAULT 24 btr_search_enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 9580: 0000000011aa1b98 16 OBJECT GLOBAL DEFAULT 24 fil_addr_null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11434: 0000000011aa1b60 8 OBJECT GLOBAL DEFAULT 24 zip_failure_threshold_pct
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 12665: 0000000011aa1b70 40 OBJECT GLOBAL DEFAULT 24 dot_ext
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 13042: 0000000011aa1b30 8 OBJECT GLOBAL DEFAULT 24 ut_rnd_ulint_counter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 13810: 0000000011aa1b48 8 OBJECT GLOBAL DEFAULT 24 srv_checksum_algorithm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 18831: 0000000011aa1bb0 48 OBJECT GLOBAL DEFAULT 24 fts_common_tables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 27713: 0000000011aa1b38 8 OBJECT GLOBAL DEFAULT 24 btr_ahi_parts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 33183: 0000000011aa1b50 8 OBJECT GLOBAL DEFAULT 24 zip_pad_max
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2386: 0000000011aa1b58 7 OBJECT LOCAL DEFAULT 24 _ZL9dict_ibfk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 5961: 0000000011aa1b68 8 OBJECT LOCAL DEFAULT 24 _ZL8eval_rnd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10509: 0000000011aa1be0 296 OBJECT GLOBAL DEFAULT 24 fts_default_stopword
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 17719: 0000000011aa1b40 1 OBJECT GLOBAL DEFAULT 24 btr_search_enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 18777: 0000000011aa1b98 16 OBJECT GLOBAL DEFAULT 24 fil_addr_null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 20631: 0000000011aa1b60 8 OBJECT GLOBAL DEFAULT 24 zip_failure_threshold_pct
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 21862: 0000000011aa1b70 40 OBJECT GLOBAL DEFAULT 24 dot_ext
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 22239: 0000000011aa1b30 8 OBJECT GLOBAL DEFAULT 24 ut_rnd_ulint_counter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 23007: 0000000011aa1b48 8 OBJECT GLOBAL DEFAULT 24 srv_checksum_algorithm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 28028: 0000000011aa1bb0 48 OBJECT GLOBAL DEFAULT 24 fts_common_tables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 36910: 0000000011aa1b38 8 OBJECT GLOBAL DEFAULT 24 btr_ahi_parts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 42380: 0000000011aa1b50 8 OBJECT GLOBAL DEFAULT 24 zip_pad_max&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The &lt;em>ut_rnd_ulint_counter&lt;/em> is stored 16 bytes away from the &lt;em>btr_search_enabled&lt;/em>. Because of this, every invalidation of &lt;em>ut_rnd_ulint_counter&lt;/em> cache line results in a cache invalidation of &lt;em>btr_search_enabled&lt;/em> on POWER, and every other variable in the &lt;em>0000000011aa1b00 to 0000000011aa1b40&lt;/em> address range for x86_64 (or to &lt;em>0000000011aa1b80&lt;/em> for POWER and ARM64, or to &lt;em>0000000011aa1c00&lt;/em> for s390). There are no rules governing the layout of these variables so it was only luck that caused x86_64 to not be affected here.&lt;/p>
&lt;p>While the contended management of &lt;em>ut_rnd_ulint_counter&lt;/em> remains an unsolved problem on MySQL-5.7, putting all the global variables into the same memory block as a way to keep them out of the same cache as other potentially frequently changed variables is a way to prevent unintended contention. Global variables are an ideal candidate for this as they are changed infrequently and are usually hot in code paths. By pulling all the global variables in the same location, this maximized the cache by using less cache lines that remain in a read only mode.&lt;/p>
&lt;p>To achieve this co-location, MySQL uses the Linux kernel mechanism of using &lt;a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-section-variable-attribute" target="_blank" rel="noopener noreferrer">section attributes on variables&lt;/a> and a linker script to bind their location. This was is described in MySQL &lt;a href="https://bugs.mysql.com/bug.php?id=97777" target="_blank" rel="noopener noreferrer">bug 97777&lt;/a> and the MariaDB task &lt;a href="https://jira.mariadb.org/browse/MDEV-21145" target="_blank" rel="noopener noreferrer">MDEV-21145&lt;/a>. The segmenting of the system global variables using this mechanism resulted in a 5.29% increase in the transactions per minute of the TPCCRunner benchmark (using MUTEXTYPE=sys).&lt;/p>
&lt;h2 id="mutex-implementations">Mutex Implementations&lt;/h2>
&lt;p>Having discovered what I thought to be a smoking gun with the &lt;em>ut_rnd_ulint_counter&lt;/em> contention being the source of throughput problems for the benchmark, the &lt;em>thread_local&lt;/em> implementation of MySQL-8.0 was back-ported to MySQL-5.7.28. Disappointingly it was discovered that the throughput was approximately the same. From a perf profile perspective, the CPU usage was no longer in the inlined &lt;em>ut_rnd_gen_ulint&lt;/em> function, it was in the &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/sync/sync0arr.cc#L451..L488" target="_blank" rel="noopener noreferrer">sync_array_wait_event&lt;/a> and &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/sync/sync0arr.cc#L333..L400" target="_blank" rel="noopener noreferrer">sync_array_reserve_cell&lt;/a> functions.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-11">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Samples: 394K of event 'cycles:ppp', Event count (approx.): 2348024370370315
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  Overhead Command Shared Object Symbol ◆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 45.48% mysqld [kernel.vmlinux] [k] _raw_spin_lock ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> __clone ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 0x8b10 ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 45.48% pfs_spawn_thread ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> handle_connection ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - do_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 45.44% dispatch_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 45.38% mysql_parse ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 45.38% mysql_execute_command ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 44.75% execute_sqlcom_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - handle_query ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 38.28% JOIN::exec ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 25.34% sub_select ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 24.85% join_read_always_key ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> handler::ha_index_read_map ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - ha_innobase::index_read ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 24.85% row_search_mvcc ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 24.85% trx_assign_read_view ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - MVCC::view_open ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 12.00% sync_array_wait_event ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 5.44% os_event::wait_low ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 2.21% os_event::wait_low ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> __pthread_mutex_unlock ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> system_call ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sys_futex ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + do_futex ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 2.04% __pthread_mutex_lock ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 0.94% pthread_cond_wait ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 2.34% __pthread_mutex_lock ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 2.28% sync_array_free_cell ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 1.60% sync_array_wait_event ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 10.69% sync_array_reserve_cell ▒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + 1.32% os_event_set&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>These functions are largely wrappers around a pthread locking implementation. From the version history these were imported from MySQL-5.0 with minor modification in 2013 compared to the pthread implementation that receives significant maintenance from the glibc community represented by major CPU architecture manufacturers.&lt;/p>
&lt;p>Thankfully, MySQL has a compile option &lt;em>-DMUTEXTYPE=sys&lt;/em> that results in &lt;a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.28/storage/innobase/include/ib0mutex.h#L110..L123" target="_blank" rel="noopener noreferrer">pthreads being used directly&lt;/a> and that increased x86 performance marginally, but much more significantly on POWER (understandable as since the sync_array elements have multiple instances on the same cache line size of 128 bytes compared to x86_64 which is 64 bytes). I’ll soon get to benchmarking these changes in more detail and generate some bug report to get this default changed in distro packages at least.&lt;/p>
&lt;h2 id="encode---another-example">Encode - Another example&lt;/h2>
&lt;p>Even while carrying out this investigation a &lt;a href="https://jira.mariadb.org/browse/MDEV-21285" target="_blank" rel="noopener noreferrer">MariaDB zulip chat&lt;/a> exposed a benchmark of &lt;a href="https://mariadb.com/kb/en/library/encode/" target="_blank" rel="noopener noreferrer">ENCODE&lt;/a> (notably deprecated in MySQL-5.7) having scaling problems. Using the exact techniques here it was quick to generate and extracted a perf profile (&lt;a href="https://jira.mariadb.org/browse/MDEV-21285" target="_blank" rel="noopener noreferrer">MDEV-21285&lt;/a>) and stack that showed every initial guess at the source of the problem – including mine – was incorrect. With the perf profile, however, the nature of the problem is quite clear – unlike the solution. That requires more thought.&lt;/p>
&lt;h2 id="reportshow-your-perf-recordings">Report/Show your perf recordings&lt;/h2>
&lt;p>Alongside its low overhead during recording, the useful aspect of perf from a DBA perspective is that perf stack traces show only the MySQL code being executed, and the frequency of its execution. There is no exposed database data, SQL queries, or table names in the output. However, the &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html#overview" target="_blank" rel="noopener noreferrer">Perf Events and tool security (item 4)&lt;/a> indicates that registers can be captured in a perf recording so be careful about sharing raw perf data.&lt;/p>
&lt;p>Once the raw perf data is processed by &lt;em>perf report&lt;/em>, with correct debug info and kernel, there are no addresses and only mysqld and kernel function names in its output. The most that is being exposing by sharing a perf report is the frequency of use of the MySQL code that was obtained externally. This should be enough to convince strict and competent managers and security people to sharing the perf recordings.&lt;/p>
&lt;p>With some realistic expectations (code can’t execute in 0 time, all of the database can’t be in CPU cache) you should now be able to show the parts of MySQL that are limiting your queries.&lt;/p>
&lt;h3 id="resulting-bug-reports">Resulting bug reports&lt;/h3>
&lt;p>MySQL:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://bugs.mysql.com/bug.php?id=97777" target="_blank" rel="noopener noreferrer">bug 97777&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bugs.mysql.com/bug.php?id=97822" target="_blank" rel="noopener noreferrer">bug 97822&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>MariaDB:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://jira.mariadb.org/browse/MDEV-21145" target="_blank" rel="noopener noreferrer">MDEV-21145 &lt;/a>&lt;/li>
&lt;li>&lt;a href="https://jira.mariadb.org/browse/MDEV-21452" target="_blank" rel="noopener noreferrer">MDEV-21452&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://jira.mariadb.org/browse/MDEV-21212" target="_blank" rel="noopener noreferrer">MDEV-21212&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>–&lt;/p>
&lt;p>&lt;em>Disclaimer: The postings on this site are the authors own and don’t necessarily represent IBM’s positions, strategies or opinions.&lt;/em>&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. Percona has not edited or tested the technical content. Views expressed are the authors’ own. When using the advice from this or any other online resource test ideas before applying them to your production systems, and always secure a working back up.&lt;/em>&lt;/p>
&lt;p>Photo by &lt;a href="https://unsplash.com/@ripato?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Ricardo Gomez Angel&lt;/a> on &lt;a href="https://unsplash.com/s/photos/perforations?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/p></content:encoded><author>Daniel Black</author><category>cacheline</category><category>MariaDB</category><category>MySQL</category><category>perf</category><category>performance</category><category>POWER</category><media:thumbnail url="https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_78060d87e7ddad26.jpg"/><media:content url="https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash_hu_17197b9de458aa08.jpg" medium="image"/></item><item><title>How To Contribute to PMM Documentation</title><link>https://percona.community/blog/2020/01/28/how-to-contribute-to-pmm-documentation/</link><guid>https://percona.community/blog/2020/01/28/how-to-contribute-to-pmm-documentation/</guid><pubDate>Tue, 28 Jan 2020 16:43:38 UTC</pubDate><description>We’d love to see more contributions towards the development and improvement of Percona Monitoring and Management (PMM), one of Percona’s most valued projects. Like all of Percona’s software, PMM is free and open-source. An area where we’d dearly love to see some community provided enhancement is in its documentation. In future blog posts, we’ll provide some insight on how to contribute to our software but… the beauty of documentation is that it’s straightforward to maintain, and you don’t even have to be a programmer to be able to provide valuable corrections and enhancements. So it’s a great place to start. In this post, we set out how you might be able to contribute to this to make PMM even better than it is already!</description><content:encoded>&lt;p>We’d love to see more contributions towards the development and improvement of &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM),&lt;/a> one of Percona’s most valued projects. Like all of Percona’s software, PMM is free and open-source. An area where we’d dearly love to see some community provided enhancement is in its documentation. In future blog posts, we’ll provide some insight on how to contribute to our software but… the beauty of documentation is that it’s straightforward to maintain, and you don’t even have to be a programmer to be able to provide valuable corrections and enhancements. So it’s a great place to start. In this post, we set out how you might be able to contribute to this to make PMM even better than it is already!&lt;/p>
&lt;h2 id="some-context">Some context&lt;/h2>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/index.html" target="_blank" rel="noopener noreferrer">PMM documentation&lt;/a> is available from the Percona website, and it is an essential part of PMM; all the tasks and functions of the developer need to be documented. There are a couple of things that might inspire you to contribute to enhancing the PMM documentation:&lt;/p>
&lt;ol>
&lt;li>It’s something you can do without feeling you need stellar programming skills&lt;/li>
&lt;li>It is useful for a large number of users.&lt;/li>
&lt;/ol>
&lt;p>By the way, if you aren’t sure where to start, there are currently more than 50 PMM documentation improvement tasks listed in &lt;a href="https://perconadev.atlassian.net/browse/PMM-5333?jql=project%20%3D%20PMM%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20Documentation" target="_blank" rel="noopener noreferrer">JIRA,&lt;/a> Percona’s fault recording system. Once you have checked out a few of those and become familiar with the documentation structure and style, you’ll probably be able to find more issues to report… or think of your own improvements.&lt;/p>
&lt;p>Even enhancements that help only a few users are very welcome.&lt;/p>
&lt;h2 id="a-simple-example">A simple example&lt;/h2>
&lt;p>This article provides a simple example which changes only a few lines of documentation, but these steps are all you need to be able to  contribute all manner of documentation improvements. Here, I focus just on the process and tools that are used to create the documentation. You’ll find more background information in the &lt;a href="https://www.percona.com/community/contributions/pmm" target="_blank" rel="noopener noreferrer">PMM Contributions Overview&lt;/a>.
&lt;figure>&lt;img width="2362" height="1250" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-1_hu_d6ff6c8039e5d555.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-1_hu_765deaadd2ac23a6.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-1_hu_3d22bec2dbec0f69.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-1_hu_3d22bec2dbec0f69.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="my-work-plan">My work plan…&lt;/h2>
&lt;h3 id="or-a-summary">…or a summary&lt;/h3>
&lt;p>Having decided I was going to be a contributor, too, I created a simple outline of what I needed to do. Here it is:&lt;/p>
&lt;ol>
&lt;li>Find an existing task or create a new one. PMM is an excellent product, but it has known &lt;a href="https://perconadev.atlassian.net/issues/?jql=project+%3D+PMM+AND+component+%3D+Documentation" target="_blank" rel="noopener noreferrer">documentation issues.&lt;/a> that I can help with&lt;/li>
&lt;li>Find the repository and install the PMM documentation on my computer so I can work out how to make changes&lt;/li>
&lt;li>Make the changes and test them.&lt;/li>
&lt;li>Send the changes to the PMM repository.&lt;/li>
&lt;li>Go through a review and verification process so that my changes can be published.&lt;/li>
&lt;/ol>
&lt;p>In the process of exploring this, I’ve written and published a manual for you, which is available in the primary documentation repository at &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm-doc.&lt;/a> This was my small contribution, and you are welcome to help improve that too!&lt;/p>
&lt;p>If you’re ready to jump in, though, let’s take a look step-by-step at what’s involved.&lt;/p>
&lt;h3 id="1-find-an-existing-task-or-create-a-new-one">1. Find an existing task or create a new one&lt;/h3>
&lt;p>Percona has identified over &lt;a href="https://perconadev.atlassian.net/issues/?jql=project%20%3D%20PMM%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20Documentation" target="_blank" rel="noopener noreferrer">50 specific documentation needs for PMM&lt;/a> as shown in &lt;a href="https://perconadev.atlassian.net/projects/PMM/issues/PMM-5075?filter=allopenissues" target="_blank" rel="noopener noreferrer">Percona’s JIRA repository of all PMM development tasks&lt;/a>. Create an account and log in to JIRA, then you can choose a current task, or create a new report to start contributing to PMM.&lt;/p>
&lt;p>In fact, for the sake of this example, while I liked the look of quite a few of the existing tasks I wanted to take the first step quickly. So I identified an improvement for the main documentation page and created a new record in JIRA. Here it is:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PMM-5012" target="_blank" rel="noopener noreferrer">https://perconadev.atlassian.net/browse/PMM-5012&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>It’s really important that you use JIRA as the starting point for any changes&lt;/strong>. This is the only way for the PMM team to find out what your intentions are and to advise you of the best approach. Through JIRA, too, you can discuss the task before you start work. If you want to work on an existing report, then I recommend that you contact the author of the task through comments in JIRA.
&lt;figure>&lt;img width="2412" height="1492" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-2_hu_123ff34ea8529289.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-2_hu_9431d76690039445.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-2_hu_8ab3e66b6a6a6ff7.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-2_hu_8ab3e66b6a6a6ff7.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="2-repository-and-installation">2. Repository and installation&lt;/h3>
&lt;p>All PMM documentation is written using the &lt;a href="https://www.sphinx-doc.org/" target="_blank" rel="noopener noreferrer">Sphinx engine markup language&lt;/a>.  We store the documentation as *.rst files inside GitHub’s &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">PMM documentation repository&lt;/a>. Sphinx allows easy publishing of various output formats such as HTML, LaTeX (for PDF), ePub, Texinfo, etc. You’ll need a GitHub account. A simple overview:&lt;/p>
&lt;ol>
&lt;li>The text is written using a unique markup language as .rst files. The syntax is very similar to markdown but with its own rules. All the rules are available on the &lt;a href="http://www.sphinx-doc.org/en/master/" target="_blank" rel="noopener noreferrer">official website&lt;/a> or can be found implemented in existing documentation.&lt;/li>
&lt;li>Source files are stored in the GitHub repository. Each version of PMM has its branch in the repository.&lt;/li>
&lt;li>The Sphinx engine collects the source code into an HTML documentation. This works very quickly.&lt;/li>
&lt;/ol>
&lt;p>In fact, you don’t even need to install Sphinx-doc, you can write or edit documentation without it just using a standard editor.
&lt;figure>&lt;img width="1228" height="692" sizes="(max-width: 1228px) 100vw, 1228px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-3_hu_a4510a799b905be5.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-3_hu_5682fd9489fc4506.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-3_hu_9939ceeef0122821.webp 1228w" src="https://percona.community/blog/2020/01/PMM-Contribute-3_hu_9939ceeef0122821.webp" alt=" " loading="lazy" />&lt;/figure> The PMM project team uses several separate repositories. See this &lt;a href="https://github.com/percona/pmm/tree/PMM-2.0" target="_blank" rel="noopener noreferrer">list of all PMM repositories in Github&lt;/a>. One of them is the &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">PMM documentation repository&lt;/a>. You’ll find a link to the documentation repository from the main PMM repository at &lt;a href="https://github.com/percona/pmm/tree/PMM-2.0" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm/tree/PMM-2.0&lt;/a>&lt;/p>
&lt;p>To begin, &lt;a href="https://help.github.com/en/github/getting-started-with-github/fork-a-repo" target="_blank" rel="noopener noreferrer">fork the PMM repository&lt;/a> under your GitHub account. You can then edit this personal fork safely, without interfering with the main repository. Later on, Percona can pull your changes into its main repository.&lt;/p>
&lt;h4 id="local-installation-of-the-documentation">Local installation of the documentation&lt;/h4>
&lt;p>Install the documentation locally on your computer. Here’s the process:&lt;/p>
&lt;ol>
&lt;li>Clone the fork repository to your environment.&lt;/li>
&lt;li>&lt;a href="http://www.sphinx-doc.org/en/master/usage/installation.html" target="_blank" rel="noopener noreferrer">Install Sphinx-doc&lt;/a> according to the instructions in the repository&lt;/li>
&lt;li>Build the documentation. Use the instruction from &lt;a href="https://github.com/percona/pmm-doc#install" target="_blank" rel="noopener noreferrer">pmm-doc repository&lt;/a> (see p.3 in Install section)&lt;/li>
&lt;li>Check the result in your browser.  You may need the Apache webserver on your computer. For example, you can use a Docker image with Apache (&lt;a href="https://hub.docker.com/_/httpd" target="_blank" rel="noopener noreferrer">link&lt;/a>). However, documentation may open in your browser without this.&lt;/li>
&lt;li>Edit some changes and rebuild.&lt;/li>
&lt;li>Check the changes in your browser.&lt;/li>
&lt;/ol>
&lt;p>… and so on. It’s essential not only to install but also to check what you can change. If you’d like more instructions, please leave a message in the comments to this post or contact me &lt;a href="mailto:community-team@percona.com">by email&lt;/a>.&lt;/p>
&lt;h3 id="3-making-changes-and-testing-them">3. Making changes and testing them&lt;/h3>
&lt;p>Now you can make changes. Two important points:&lt;/p>
&lt;ol>
&lt;li>If you aren’t sure how make changes correctly, take a look at how others do it. There are already plenty of changes in the documentation; and you should be able to see them.&lt;/li>
&lt;li>It’s essential to make changes properly, otherwise your hard work will be wasted.&lt;/li>
&lt;/ol>
&lt;p>We have already selected or created a task in JIRA, and we will need its ID. The JIRA task ID is used as an identifier for the JIRA and GitHub bundle.&lt;/p>
&lt;p>We need to create a new git branch. When creating a branch, correctly name it using the formula: JIRA_ID_SHORTTITLE. For example, my GitHub user is dbazhenov, and the changes I’m making are related to the JIRA task PMM-5012, so here’s the command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git checkout -b PMM-5012_dbazhenov_introduction&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>So… you found the right page and made some changes. If you created a new page, there are examples in the existing documentation. In this case, you need to create a new page file and include it in the toctree level below. If you need help with that, please just ask.&lt;/p>
&lt;p>Now save your changes to git and be sure to call the commit correctly. What do I mean by that? Well, be sure to use the task ID and describe in detail the change you’ve made. Here’s my example:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m "PMM-5012 PostgreSQL and ProxySQL have been added to the home page"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, build the documentation and check the result in your browser. If you get warnings during the build, this is mostly likely to be due to using different versions of Sphinx and nothing to worry about.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="826" height="340" sizes="(max-width: 826px) 100vw, 826px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-7-warn_hu_ac293cbcec97ece1.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-7-warn_hu_11a235e0ce317806.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-7-warn_hu_55240e59a0115aea.webp 826w" src="https://percona.community/blog/2020/01/PMM-Contribute-7-warn_hu_55240e59a0115aea.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>When you see the documentation, don’t worry that it’s not CSS or JavaScript, only pure HTML. In due course, it will be built into the current percona.com website and will inherit its styling from there.&lt;/p>
&lt;h3 id="4-saving-the-result-and-contributing">4. Saving the result and contributing&lt;/h3>
&lt;p>This is where you send your work to the PMM team. First, you have to send your branch to your own fork. That’s straightforward:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> git push origin PMM-5012_dbazhenov&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, open your repository and check the results. In particular, make sure that your branch holds only the changed files. It’s possible that additional files have been uploaded. To check the result, create &lt;a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests" target="_blank" rel="noopener noreferrer">a pull request&lt;/a> in the master branch of your repository. This will give you a list of the changes that you’ve made.&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2112" height="1166" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-5_hu_bd636fc40d0c091a.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-5_hu_fb2433246b6a1c28.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-5_hu_70705c867414ebdc.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-5_hu_70705c867414ebdc.webp" alt=" " loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="1264" height="840" sizes="(max-width: 1264px) 100vw, 1264px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-4_hu_b263c46c79ab75e5.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-4_hu_d9b226f506078494.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-4_hu_1d6446f8250f5b65.webp 1264w" src="https://percona.community/blog/2020/01/PMM-Contribute-4_hu_1d6446f8250f5b65.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>Once you’ve checked that the pull request has only the intended changes, you can make a second pull request, but this time it’s to the Percona repository.&lt;/p>
&lt;p>Here’s my pull request: &lt;a href="https://github.com/percona/pmm-doc/pull/45" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm-doc/pull/45&lt;/a>&lt;/p>
&lt;p>
&lt;figure>&lt;img width="2324" height="1516" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-6_hu_bb11eb0ca2aa8c4.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-6_hu_996ee138f3502766.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-6_hu_d75a75429178ddb7.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-6_hu_d75a75429178ddb7.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h3 id="5-passing-a-review">5. Passing a review&lt;/h3>
&lt;p>All submissions are thoroughly reviewed before being released. This guarantees the quality and safety of PMM. Even if it’s “just” documentation, it has a very important role to play in the user experience.&lt;/p>
&lt;p>You will also need to confirm the Contributor License Agreement.&lt;/p>
&lt;p>Once I’d submitted my changes, I waited a little while, and then the Percona team checked my work and sent it back to me for improvement. I made the necessary changes and – this is an important point – I sent them to the &lt;strong>same pull request&lt;/strong>.
&lt;figure>&lt;img width="2126" height="1278" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_86a0dfb81261a808.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_fe83c3f32e8896ee.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_a99e870467e74d6e.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_a99e870467e74d6e.webp" alt=" " loading="lazy" />&lt;/figure>
&lt;figure>&lt;img width="1656" height="1120" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-9_hu_e771e503372ee6b4.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-9_hu_3cceab5d266e3ef1.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-9_hu_1d42c12973804bbc.webp 1400w" src="https://percona.community/blog/2020/01/PMM-Contribute-9_hu_1d42c12973804bbc.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h4 id="release">Release&lt;/h4>
&lt;p>There’s nothing for you to do here, the Percona team have to create releases of software and documentation.&lt;/p>
&lt;p>After a few days, Percona published my changes to the PMM documentation site.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/doc/percona-monitoring-and-management/2.x/index.html" target="_blank" rel="noopener noreferrer">https://www.percona.com/doc/percona-monitoring-and-management/2.x/index.html&lt;/a>&lt;/p>
&lt;p>That’s how I ended up on the list of pmm-doc contributors.
&lt;figure>&lt;img width="1026" height="642" sizes="(max-width: 1026px) 100vw, 1026px" srcset="https://percona.community/blog/2020/01/PMM-Contribute-10_hu_d2eadab4efa2f9ad.webp 768w, https://percona.community/blog/2020/01/PMM-Contribute-10_hu_afb75a93f1c45218.webp 480w, https://percona.community/blog/2020/01/PMM-Contribute-10_hu_6877fbf349716cc8.webp 1026w" src="https://percona.community/blog/2020/01/PMM-Contribute-10_hu_6877fbf349716cc8.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Contributing to documentation is a great way to start your journey as an open source contributor, especially if you are not too familiar with git and GitHub. If you’d like to start contributing to open source, then I recommend you try contributing to the PMM documentation. Instructions here: &lt;a href="https://github.com/percona/pmm-doc" target="_blank" rel="noopener noreferrer">https://github.com/percona/pmm-doc&lt;/a>&lt;/p>
&lt;p>All the same, I realize that documentation is not for everyone, even as a means of introduction. So here are some ideas and options for contributing to PMM in other ways: &lt;a href="https://www.percona.com/community/contributions/pmm" target="_blank" rel="noopener noreferrer">https://www.percona.com/community/contributions/pmm&lt;/a>&lt;/p>
&lt;p>As already reported, I more than happy to help you out. Just sent me an email to &lt;a href="mailto:community-team@percona.com">community-team@percona.com&lt;/a> and add “PMM Community” to your subject line so that my colleagues know that the email’s for me. Good luck!&lt;/p></content:encoded><author>Daniil Bazhenov</author><category>daniil.bazhenov</category><category>contributing</category><category>contributions</category><category>contributors</category><category>documentation</category><category>Open Source Databases</category><category>Percona Monitoring and Management</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2020/01/PMM-Contribute-10_hu_7cafffa62fa0b179.jpg"/><media:content url="https://percona.community/blog/2020/01/PMM-Contribute-10_hu_e8c3686c85efc412.jpg" medium="image"/></item><item><title>Disk of Yesteryear Compared to Today’s SSD Drives</title><link>https://percona.community/blog/2020/01/17/disk-of-yesteryear-compared-to-todays-ssd-drives/</link><guid>https://percona.community/blog/2020/01/17/disk-of-yesteryear-compared-to-todays-ssd-drives/</guid><pubDate>Fri, 17 Jan 2020 16:48:46 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img width="960" height="572" sizes="(max-width: 960px) 100vw, 960px" srcset="https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_d10415a5fc5aadbd.webp 768w, https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_d33eb0b0cb74301d.webp 480w, https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_e4f73247b354484a.webp 960w" src="https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_e4f73247b354484a.webp" alt=" " loading="lazy" />&lt;/figure>&lt;/p>
&lt;p>In my [last blog post](&lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">https://www.percona.com/&lt;/a>
community-blog/2019/08/01/how-to-build-a-percona-server-stack-on-a-raspberry-pi-3/) I showed you how to get the entire Percona “Stack" up and running on a Raspberry Pi. This time around, I would like to show the impact on performance between using an SSD hard disk and a standard hard disk.&lt;/p>
&lt;p>Disk performance is a key factor in &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a> (or any RDB platform) performance on a Raspberry Pi 4.&lt;/p>
&lt;h2 id="test-set-up">Test set up&lt;/h2>
&lt;p>Each test below was run three times per Hard Disk and I took the best of the three for comparison. Hardware&lt;/p>
&lt;ul>
&lt;li>Raspberry Pi 4+ with 4GB ram.&lt;/li>
&lt;li>Disk 1: USB3 Western Digital My Passport Ultra, 1TB&lt;/li>
&lt;li>Disk 2: USB3 KEXIN 240GB Portable External SSD Drive&lt;/li>
&lt;/ul>
&lt;p>Hardware stayed consistent during test, except for the hard disk that were switched from KEXIN to Western Digital drive. Software&lt;/p>
&lt;ul>
&lt;li>Raspbian Buster&lt;/li>
&lt;li>Persona Server Version: 5.7.27-30 built from source. See the above BLOG for install instructions.&lt;/li>
&lt;li>Sysbench 1.0.17&lt;/li>
&lt;/ul>
&lt;p>Sample my.cnf&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-0">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[mysqld]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">port = 3306
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">socket = /var/lib/mysql/mysql.sock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pid-file = /var/lib/mysql/mysqld.pid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">basedir = /usr/local/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">datadir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmpdir = /data0/mysql/tmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">general_log_file = /var/log/mysql/mysql-general.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log-error = /var/log/mysql/mysqld.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log_file = /var/log/mysql/log/slow_query.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slow_query_log = 0 # Slow query log off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lc-messages-dir = /usr/local/mysql/share
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">plugin_dir = /usr/local/mysql/lib/mysql/plugin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log-bin = /data0/mysql/binlog/mysql-bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sync_binlog = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">expire_logs_days = 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">server-id = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">binlog_format = mixed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_allowed_packet = 64M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_connections = 50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max_user_connections = 40
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_cache_size=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">query_cache_type=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_data_home_dir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_group_home_dir = /data0/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_files_in_group = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_size = 1536M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_file_size = 64M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_buffer_size = 8M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_log_at_trx_commit = 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#innodb_flush_log_at_trx_commit = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_lock_wait_timeout = 50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_flush_method = O_DIRECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_file_per_table = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_buffer_pool_instances = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">skip-name-resolve=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">thread_pool_size=20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_temp_data_file_path = ../tmp/ibtmp1:12M:autoextend:max:8G&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Sysbench MySQL test prep step:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-1">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sysbench --db-driver=mysql —mysql-db=sbtest --oltp-table-size=500000 --oltp-tables-count=10 --threads=8 --mysql-host= --mysql-port=3306 --mysql-user= --mysql-password=
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/usr/share/sysbench/tests/include/oltp_legacy/parallel_prepare.lua run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="test-1">Test 1&lt;/h2>
&lt;p>This was done using the: KEXIN 240GB Portable External SSD Drive. Sysbench command:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-2">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sysbench --db-driver=mysql --mysql-db=sbtest --report-interval=2 --mysql-table-engine=innodb --oltp-table-size=500000 --oltp-tables-count=10 --oltp-test-mode=complex --threads=10 --time=150 —mysql-host= --mysql-port=3306 —mysql-user= —mysql-password= /usr/share/sysbench/tests/include/oltp_legacy/oltp.lua run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Output:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-3">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SQL statistics:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queries performed:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> read: 486542
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> write: 139012
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> other: 69506
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total: 695060
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> transactions: 34753 (231.62 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queries: 695060 (4632.45 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ignored errors: 0 (0.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> reconnects: 0 (0.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">General statistics:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total time: 150.0362s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total number of events: 34753
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Latency (ms):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> min: 20.28
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> avg: 43.16
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max: 94.32
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 95th percentile: 57.87
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sum: 1500044.61
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Threads fairness:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events (avg/stddev): 3475.3000/368.77
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execution time (avg/stddev): 150.0045/0.01&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see the performance with the KEXIN (SSD) Drive was pretty good:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-4">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">transactions: 34753 (231.62 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queries: 695060 (4632.45 per sec.)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="test-2">Test 2&lt;/h2>
&lt;p>This was done using the: Western Digital My Passport Ultra 1TB drive.&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-5">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">SQL statistics:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queries performed:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> read: 60984
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> write: 17424
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> other: 8712
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total: 87120
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> transactions: 4356 (29.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> queries: 87120 (579.94 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ignored errors: 0 (0.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> reconnects: 0 (0.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">General statistics:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total time: 150.2160s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total number of events: 4356
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Latency (ms):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> min: 23.26
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> avg: 344.75
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max: 1932.12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 95th percentile: 733.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> sum: 1501739.03
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Threads fairness:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events (avg/stddev): 435.6000/5.71
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execution time (avg/stddev): 150.1739/0.05&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>As you can see the performance on the Western Digital Drive was really bad:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-6">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">transactions: 4356 (29.00 per sec.)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">queries: 87120 (579.94 per sec.)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="disk-io-tests">Disk IO Tests&lt;/h3>
&lt;p>KEXIN:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard">
&lt;span class="code-block__copy-default">Copy&lt;/span>
&lt;span class="code-block__copy-success" aria-hidden="true">Copied!&lt;/span>
&lt;/button>
&lt;/div>
&lt;div class="code-block__content" id="codeblock-7">
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Operations performed: 208123 Read, 138748 Write, 443904 Other = 790775 Total
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Read 3.1757Gb Written 2.1171Gb Total transferred 5.2928Gb (18.066Mb/sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1156.24 Requests/sec executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Test execution summary:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total time: 300.0004s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total number of events: 346871
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total time taken by event execution: 113.1569
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> per-request statistics:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> min: 0.02ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> avg: 0.33ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max: 31.07ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> approx. 95 percentile: 0.60ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Threads fairness:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events (avg/stddev): 346871.0000/0.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execution time (avg/stddev): 113.1569/0.00&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Western Digital:&lt;/p>
&lt;div class="code-block">
&lt;div class="code-block__header">&lt;button class="code-block__copy