<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>Fri, 12 Jun 2026 12:40:55 PDT</lastBuildDate><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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_95dee6fd64dddcf9.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_4433d9913f547138.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-website-search_hu_46bc819bedb76e43.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-website-search.png" alt="Percona.community website search" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_392264fd5a0c62b8.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_a75f633f2ca04c29.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status_hu_95b0cc46f0362fa0.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status.png" alt="Admin dashboard - Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_721d55e5a3792bc2.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_b635aa7029ac1cae.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index_hu_740bdac5010847b3.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index.png" alt="Admin dashboard - Indexing" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_97f09574e9eb7911.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_f1a285d61f7a3225.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking_hu_dbba193000ce68c2.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking.png" alt="Admin dashboard - Chunking" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_13abbd707a20d9f0.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_5d57b3a6a188698d.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details_hu_30aae0df706fcd66.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details.png" alt="Admin dashboard, document details (pages) and chunks" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_d79598b8ca850612.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_c3a88fcd9c3db3bb.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running_hu_ced900f69aeb752b.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running.png" alt="Admin dashboard - Index Running" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_6b1aaa8398bc7b9d.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_7e7d6bdaf51d3b5e.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history_hu_47492d14ae213e11.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history.png" alt="Admin dashboard - Index history" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_8f9c3e24fc464efd.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_387d2c3dd4957f60.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats_hu_2884c4942f70e9a.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats.png" alt="Admin dashboard - Index overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_98dda8a8ad9ab747.png 480w, https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_f6f91298f9e7cad4.png 768w, https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores_hu_58520e0bfb232e4f.png 1400w"
src="https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores.png" alt="Widget search results with similarity scores" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_1aed4a040d28b7b9.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_c8e667a1295affbc.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-kubernetes_hu_457089cf99138b9d.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-kubernetes.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Introduction" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_e17c10bd2c910059.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_763a62f35cf3ba78.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-pz-talks_hu_68618e129ed1968c.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-pz-talks.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Widget" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-page_hu_373d8b9c570b1f01.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-page_hu_59cc5ddb23f112e.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-page_hu_1bc351eb6064d38e.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-page.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Search Page" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_ace1c17ba90d7bd6.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_77ea840f613e7cf0.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-search_hu_af7e16b9526077e2.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-demo-search.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search" />&lt;/figure>&lt;/p>
&lt;p>Search history: making search better&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_30a51dbe53dea472.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_b18750ee7cc8d840.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-history_hu_c1d2ff3cc57d0a93.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-demo-history.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search history" />&lt;/figure>&lt;/p>
&lt;p>Indexing status, to see when search data was last updated&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_d89913f0e2a080fc.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_42a72521c7aa351a.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-status_hu_41accae7092373ef.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-demo-status.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexing status" />&lt;/figure>&lt;/p>
&lt;p>Indexed documents with the ability to view data and chunks.&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_d156347b728f32ec.png 480w, https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_4f4628d7559ff67e.png 768w, https://percona.community/blog/2026/05/search-part-1-intro-demo-pages_hu_6aa15acad6edfbb9.png 1400w"
src="https://percona.community/blog/2026/05/search-part-1-intro-demo-pages.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexed documents" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_3e8546552d12a296.png 480w, https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_8be3a66ff6260516.png 768w, https://percona.community/blog/2026/05/Jan-pgb-news-1_hu_3285a98c5263694f.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-news-1.png" alt="blog/2026/05/Jan-pgb-news-1.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_3d06fa37ce7c64f7.png 480w, https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_d6b5c71dfd271e3c.png 768w, https://percona.community/blog/2026/05/Jan-pgb-not-dead_hu_dfb87937caed8718.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-not-dead.png" alt="blog/2026/05/Jan-pgb-not-dead.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_6b0c9e336f40f858.png 480w, https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_41e0c7e536ad9509.png 768w, https://percona.community/blog/2026/05/Jan-pgb-news-2_hu_447b1ac44c8ef4cd.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-news-2.png" alt="blog/2026/05/Jan-pgb-news-2.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_11dcce2c6b7feb04.png 480w, https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_22b22ad679a99286.png 768w, https://percona.community/blog/2026/05/Jan-pgb-back-cover_hu_7972850c430e75cd.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-back-cover.png" alt="blog/2026/05/Jan-pgb-back-cover.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_9d3302bf6b51a456.png 480w, https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_f8f6fb075d79016b.png 768w, https://percona.community/blog/2026/05/Jan-pgb-news-3_hu_1c719d347f4bb955.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-news-3.png" alt="blog/2026/05/Jan-pgb-news-3.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/Jan-pgb-back_hu_79be5fdc8f494745.png 480w, https://percona.community/blog/2026/05/Jan-pgb-back_hu_697a1fc88eb9abab.png 768w, https://percona.community/blog/2026/05/Jan-pgb-back_hu_975220b27573d3eb.png 1400w"
src="https://percona.community/blog/2026/05/Jan-pgb-back.png" alt="blog/2026/05/Jan-pgb-back.png" />&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 src="https://percona.community/blog/2026/05/mongodb-desai.png" alt="ceo" />&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 src="https://percona.community/blog/2026/05/mongodb-team.png" alt="team" />&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 src="https://percona.community/blog/2026/05/mongodb-skills.png" alt="skills" />&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 src="https://percona.community/blog/2026/05/mongodb-badges.png" alt="badges" />&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 src="https://percona.community/blog/2026/05/mongodb-eleven.png" alt="eleven" />&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 src="https://percona.community/blog/2026/05/mongodb-workshop.png" alt="badges" />&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 src="https://percona.community/blog/2026/05/mongodb-podcast.png" alt="podcast" />&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 src="https://percona.community/blog/2026/05/mongodb-socks.png" alt="podcast" />&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 src="https://percona.community/blog/2026/05/mongodb-percona.png" alt="bye" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/05/community-team-with-names_hu_26e74b80b66db48f.jpeg 480w, https://percona.community/blog/2026/05/community-team-with-names_hu_79faa688d303bb05.jpeg 768w, https://percona.community/blog/2026/05/community-team-with-names_hu_98dd9cfce452f495.jpeg 1400w"
src="https://percona.community/blog/2026/05/community-team-with-names.jpeg" alt="The Percona Community team in Antalya" />&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 src="https://percona.community/blog/2026/05/ai-gardening.png" alt=" " />&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 src="https://percona.community/blog/2026/05/ai-running.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_21d0a2a0e79b6d86.png 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_cd5177ea9f17ca1.png 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-reddit_hu_c625e2c0680f79df.png 1400w"
src="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit.png" alt="blog/2026/04/opensourcedoesntdie-reddit.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_7ed35b5e1366b026.png 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_303aec4bbb29b248.png 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource_hu_2d3e5e8f6983dd75.png 1400w"
src="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource.png" alt="blog/2026/04/opensourcedoesntdie-thisisopensource.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_9d8f2901b2dbffb2.png 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_1177fb345850cd40.png 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance_hu_543cb6ea24c56058.png 1400w"
src="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance.png" alt="blog/2026/04/opensourcedoesntdie-maintenance.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_d340d3c90410198b.png 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_130b9f8470a6494d.png 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan_hu_b0416615bb00fd5a.png 1400w"
src="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan.png" alt="blog/2026/04/opensourcedoesntdie-youngpadawan.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_5b52e8fa6672e280.png 480w, https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_4794c91ea154d51d.png 768w, https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed_hu_14db82f50fcc1867.png 1400w"
src="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed.png" alt="blog/2026/04/opensourcedoesntdie-allyouneed.png" />&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 src="https://percona.community/blog/2026/04/Jan-david-li.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/Jan-forks_hu_3ce0289596777ce0.png 480w, https://percona.community/blog/2026/04/Jan-forks_hu_40f768e23b6a32bb.png 768w, https://percona.community/blog/2026/04/Jan-forks_hu_6da1a6a8a4872f49.png 1400w"
src="https://percona.community/blog/2026/04/Jan-forks.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/Jan-comic-neb_hu_b806f6ed643f3d53.png 480w, https://percona.community/blog/2026/04/Jan-comic-neb_hu_a06fc3afc9cf61de.png 768w, https://percona.community/blog/2026/04/Jan-comic-neb_hu_12272b8b98cc35f3.png 1400w"
src="https://percona.community/blog/2026/04/Jan-comic-neb.png" alt=" " />&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 src="https://percona.community/blog/2026/04/Jan-david-money.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/04/Jan-what-now_hu_75029800cf3eb844.png 480w, https://percona.community/blog/2026/04/Jan-what-now_hu_f0bf47fe17fb656b.png 768w, https://percona.community/blog/2026/04/Jan-what-now_hu_8bc230962af6f9dc.png 1400w"
src="https://percona.community/blog/2026/04/Jan-what-now.png" alt=" " />&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 src="https://percona.community/blog/2026/04/innodb_buffer_pool_diagram.png" alt="InnoDB Buffer Pool Diagram" />&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 src="https://percona.community/blog/2026/03/impersonation-slack-conversation.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-account-register_hu_147a71b9e566c6cf.png 480w, https://percona.community/blog/2026/03/ping-account-register_hu_ffdee5297e98e834.png 768w, https://percona.community/blog/2026/03/ping-account-register_hu_6cc9826547506a3b.png 1400w"
src="https://percona.community/blog/2026/03/ping-account-register.png" alt=" " />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Fill in the required details to complete the profile
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-profile_hu_75a5860c63f3a0c0.png 480w, https://percona.community/blog/2026/03/ping-profile_hu_6313c8816871d06f.png 768w, https://percona.community/blog/2026/03/ping-profile_hu_e0413c9aba3cb820.png 1400w"
src="https://percona.community/blog/2026/03/ping-profile.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-sign-on_hu_11a576b182b7b00.png 480w, https://percona.community/blog/2026/03/ping-sign-on_hu_379eb23d9eef47a3.png 768w, https://percona.community/blog/2026/03/ping-sign-on_hu_1d08dab1b5a3fcb7.png 1400w"
src="https://percona.community/blog/2026/03/ping-sign-on.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-admin-console_hu_a512896ab1a692de.png 480w, https://percona.community/blog/2026/03/ping-admin-console_hu_4bcd7bfb8e965006.png 768w, https://percona.community/blog/2026/03/ping-admin-console_hu_a8363ca04216abd0.png 1400w"
src="https://percona.community/blog/2026/03/ping-admin-console.png" alt=" " />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Provide an environment name and click on Finish
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-environment_hu_2f8eaab5228e733c.png 480w, https://percona.community/blog/2026/03/ping-environment_hu_ff1c0fa71f6998c5.png 768w, https://percona.community/blog/2026/03/ping-environment_hu_148ab9c15f700c9b.png 1400w"
src="https://percona.community/blog/2026/03/ping-environment.png" alt=" " />&lt;/figure>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Once the environment is created, click on Manage environment.
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-manage-environment_hu_8923c797a03deb3e.png 480w, https://percona.community/blog/2026/03/ping-manage-environment_hu_5eab23433df74cd0.png 768w, https://percona.community/blog/2026/03/ping-manage-environment_hu_a8e58c92af35f3d3.png 1400w"
src="https://percona.community/blog/2026/03/ping-manage-environment.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-new-application_hu_dc531e2ea429b722.png 480w, https://percona.community/blog/2026/03/ping-new-application_hu_351851c18d8b9709.png 768w, https://percona.community/blog/2026/03/ping-new-application_hu_735107d5d29452bd.png 1400w"
src="https://percona.community/blog/2026/03/ping-new-application.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-created-application_hu_36632ecb12eb0f5a.png 480w, https://percona.community/blog/2026/03/ping-created-application_hu_d0be474c53e21490.png 768w, https://percona.community/blog/2026/03/ping-created-application_hu_b117dbb1709516a2.png 1400w"
src="https://percona.community/blog/2026/03/ping-created-application.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-click-add-scope_hu_b4872d4f46a208b5.png 480w, https://percona.community/blog/2026/03/ping-click-add-scope_hu_79c2e057da3347cc.png 768w, https://percona.community/blog/2026/03/ping-click-add-scope_hu_f41ad8e3d625f93c.png 1400w"
src="https://percona.community/blog/2026/03/ping-click-add-scope.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-add-scope-name_hu_fa8a2d96ebed0002.png 480w, https://percona.community/blog/2026/03/ping-add-scope-name_hu_bae4df0905fcb2cd.png 768w, https://percona.community/blog/2026/03/ping-add-scope-name_hu_666d5e41bb4e914f.png 1400w"
src="https://percona.community/blog/2026/03/ping-add-scope-name.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-application-config_hu_8e49f7ef71d77ae.png 480w, https://percona.community/blog/2026/03/ping-application-config_hu_a866443853d442cb.png 768w, https://percona.community/blog/2026/03/ping-application-config_hu_7b253424280375f6.png 1400w"
src="https://percona.community/blog/2026/03/ping-application-config.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-application-add-scope_hu_1c0cc221ea08f44c.png 480w, https://percona.community/blog/2026/03/ping-application-add-scope_hu_3e7d29853d48a1ab.png 768w, https://percona.community/blog/2026/03/ping-application-add-scope_hu_81572ab16d9e85a9.png 1400w"
src="https://percona.community/blog/2026/03/ping-application-add-scope.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-create-user_hu_7bb59c2d8041fdc1.png 480w, https://percona.community/blog/2026/03/ping-create-user_hu_3f790fd42472b736.png 768w, https://percona.community/blog/2026/03/ping-create-user_hu_e5633ccc8368d1ae.png 1400w"
src="https://percona.community/blog/2026/03/ping-create-user.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-enable-user_hu_aa2cbb8b3ac6ae1c.png 480w, https://percona.community/blog/2026/03/ping-enable-user_hu_6399363a5a12207c.png 768w, https://percona.community/blog/2026/03/ping-enable-user_hu_729bb3966064e850.png 1400w"
src="https://percona.community/blog/2026/03/ping-enable-user.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-user-login_hu_62d13adb65a6ffce.png 480w, https://percona.community/blog/2026/03/ping-user-login_hu_f56482cbf6b6bf44.png 768w, https://percona.community/blog/2026/03/ping-user-login_hu_a81255dcf859f86f.png 1400w"
src="https://percona.community/blog/2026/03/ping-user-login.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>Next, it will prompt you to enter the activation code.
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-activation-code_hu_91c326ab0f750f18.png 480w, https://percona.community/blog/2026/03/ping-activation-code_hu_899785f8765c8351.png 768w, https://percona.community/blog/2026/03/ping-activation-code_hu_2b0f8ada7512ef0d.png 1400w"
src="https://percona.community/blog/2026/03/ping-activation-code.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/03/ping-approve-user_hu_f2f4545a7ef299d4.png 480w, https://percona.community/blog/2026/03/ping-approve-user_hu_ea0b8e5b47433cf6.png 768w, https://percona.community/blog/2026/03/ping-approve-user_hu_15ec2b8a23930447.png 1400w"
src="https://percona.community/blog/2026/03/ping-approve-user.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_2780452978701797.png 480w, https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_a9c5c37e1bd0636.png 768w, https://percona.community/blog/2026/02/Jan-glibc-confusion_hu_a5053be3c4e47882.png 1400w"
src="https://percona.community/blog/2026/02/Jan-glibc-confusion.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/Jan-always-Feb17_hu_c7880ac6c548f907.png 480w, https://percona.community/blog/2026/02/Jan-always-Feb17_hu_275ad8c4abe36f51.png 768w, https://percona.community/blog/2026/02/Jan-always-Feb17_hu_78e6d24fc4af4675.png 1400w"
src="https://percona.community/blog/2026/02/Jan-always-Feb17.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-all_hu_e712242d8225486.png 480w, https://percona.community/blog/2026/02/fosdem-all_hu_419bf189b512c36b.png 768w, https://percona.community/blog/2026/02/fosdem-all_hu_9d4edb54da418973.png 1400w"
src="https://percona.community/blog/2026/02/fosdem-all.png" alt="Fosdem intro" />&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 src="https://percona.community/blog/2026/02/fosdem-speakers.png" alt="Fosdem speakers" />&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 src="https://percona.community/blog/2026/02/fosdem-pg.png" alt="Fosdem speakers" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-peter_hu_b3c4adfd61074afa.jpg 480w, https://percona.community/blog/2026/02/fosdem-peter_hu_1fbd35cb821b5c8c.jpg 768w, https://percona.community/blog/2026/02/fosdem-peter_hu_be436136b025998e.jpg 1400w"
src="https://percona.community/blog/2026/02/fosdem-peter.jpg" alt="Fosdem speakers" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_2469df151a62abfb.jpeg 480w, https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_a33fe6004c89e577.jpeg 768w, https://percona.community/blog/2026/02/fosdem-mysql-summit_hu_3ff493e53e6b551a.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-mysql-summit.jpeg" alt="Fosdem MySQL Summit" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-rockstars_hu_1bffe1196cbfd19a.jpeg 480w, https://percona.community/blog/2026/02/fosdem-rockstars_hu_3fd61445be2327bd.jpeg 768w, https://percona.community/blog/2026/02/fosdem-rockstars_hu_eebd57b07de5904b.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-rockstars.jpeg" alt="Fosdem postgressql" />&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 src="https://percona.community/blog/2026/02/fosdem-booth.png" alt="Fosdem postgressql" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-database-01_hu_24d435d202eb15dc.jpeg 480w, https://percona.community/blog/2026/02/fosdem-database-01_hu_5bfbaa44d8e570f6.jpeg 768w, https://percona.community/blog/2026/02/fosdem-database-01_hu_aa83876523e835fa.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-database-01.jpeg" alt="Fosdem postgressql" />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-database-02_hu_1fd300fdd4485d45.jpeg 480w, https://percona.community/blog/2026/02/fosdem-database-02_hu_3aa1f8ab0f221851.jpeg 768w, https://percona.community/blog/2026/02/fosdem-database-02_hu_a647570fbb1f1d95.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-database-02.jpeg" alt="Fosdem postgressql" />&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 src="https://percona.community/blog/2026/02/fosdem-percona-20.png" alt="Fosdem postgressql" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/pgday-slonik_hu_89ebc067372229f.jpeg 480w, https://percona.community/blog/2026/02/pgday-slonik_hu_f4da3e9e56bdb45d.jpeg 768w, https://percona.community/blog/2026/02/pgday-slonik_hu_dc2c2f7307992d6b.jpeg 1400w"
src="https://percona.community/blog/2026/02/pgday-slonik.jpeg" alt="PGDay Kai and Slonik" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_5a36d046c38bf9f4.jpg 480w, https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_447a561bf987611e.jpg 768w, https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering_hu_f37053b55358f597.jpg 1400w"
src="https://percona.community/blog/2026/02/fosdem-pgbooth-volunteering.jpg" alt="PGDay Kai PG Booth Volunteering" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-slonik_hu_3f785c72b8014059.jpeg 480w, https://percona.community/blog/2026/02/fosdem-slonik_hu_7b4ba0e740713ceb.jpeg 768w, https://percona.community/blog/2026/02/fosdem-slonik_hu_72b2283e2a31412.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-slonik.jpeg" alt="FOSDEM Sakila and Slonik" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_1424cc475b89471a.jpeg 480w, https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_72d624d5738f7a58.jpeg 768w, https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering_hu_6c8ccbd6d626db60.jpeg 1400w"
src="https://percona.community/blog/2026/02/fosdem-perconabooth-volunteering.jpeg" alt="PGDay Kai PG Booth Volunteering" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/02/hackorum-topics_hu_f74adce92511394d.png 480w, https://percona.community/blog/2026/02/hackorum-topics_hu_2d8b2a0a8d9093e2.png 768w, https://percona.community/blog/2026/02/hackorum-topics_hu_940c38da7bb7edde.png 1400w"
src="https://percona.community/blog/2026/02/hackorum-topics.png" alt="Hackorum topics overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/01/keycloak_step1_realm_hu_13dd5878f6232ecd.png 480w, https://percona.community/blog/2026/01/keycloak_step1_realm_hu_ba7d507019c7aadc.png 768w, https://percona.community/blog/2026/01/keycloak_step1_realm_hu_716a79699a710e77.png 1400w"
src="https://percona.community/blog/2026/01/keycloak_step1_realm.png" alt="Creating a new realm in Keycloak" />&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 src="https://percona.community/blog/2026/01/keycloak_step2_user.png" alt="Creating a user in Keycloak" />&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 src="https://percona.community/blog/2026/01/keycloak_step3_user.png" alt="Setting user credentials in Keycloak" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/01/keycloak_step4_client_hu_fda177164b10466b.png 480w, https://percona.community/blog/2026/01/keycloak_step4_client_hu_9e1dbbc8e4be2d0.png 768w, https://percona.community/blog/2026/01/keycloak_step4_client_hu_2689efc683ed8dc5.png 1400w"
src="https://percona.community/blog/2026/01/keycloak_step4_client.png" alt="Creating a client in Keycloak" />&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 src="https://percona.community/blog/2026/01/keycloak_step5_client.png" alt="Configuring client authentication flow in Keycloak" />&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 src="https://percona.community/blog/2026/01/keycloak_step6_client.png" alt="Configuring consent screen in Keycloak" />&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 sizes="100vw" srcset="https://percona.community/blog/2026/01/keycloak_step7_scope_hu_e89d0c99b0443b74.png 480w, https://percona.community/blog/2026/01/keycloak_step7_scope_hu_39192cb2aaf14e80.png 768w, https://percona.community/blog/2026/01/keycloak_step7_scope_hu_a5244b52767e3fb9.png 1400w"
src="https://percona.community/blog/2026/01/keycloak_step7_scope.png" alt="Creating a client scope in Keycloak" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_27417eb40537532.png 480w, https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_b4ab6a7b92143c78.png 768w, https://percona.community/blog/2025/12/Jan-Hippo-PG_hu_b19756f6a567aadf.png 1400w"
src="https://percona.community/blog/2025/12/Jan-Hippo-PG.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2025/12/Jan-2026_hu_4fc301867a663736.png 480w, https://percona.community/blog/2025/12/Jan-2026_hu_7e6125bb16d5b43a.png 768w, https://percona.community/blog/2025/12/Jan-2026_hu_597f77f9038a6467.png 1400w"
src="https://percona.community/blog/2025/12/Jan-2026.png" alt=" " />&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 src="https://percona.community/blog/2025/12/pg_oidc_cache.png" alt=" " />&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 src="https://percona.community/blog/2025/12/img1.png" alt="Percona Connect London 2025" />&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 src="https://percona.community/blog/2025/12/img2.png" alt="Percona Connect London 2025" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2025/12/img3.png" alt="Percona Connect London 2025" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/12/extra_hu_4cb02671c333d4f0.png 480w, https://percona.community/blog/2025/12/extra_hu_f013f2a78c02ab84.png 768w, https://percona.community/blog/2025/12/extra_hu_b5bf0b10e32f0c99.png 1400w"
src="https://percona.community/blog/2025/12/extra.png" alt="Percona Connect London 2025" />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/12/img4_hu_d815369516251e84.png 480w, https://percona.community/blog/2025/12/img4_hu_a0219eb57316ebe2.png 768w, https://percona.community/blog/2025/12/img4_hu_d7ca9ec236e7f32b.png 1400w"
src="https://percona.community/blog/2025/12/img4.png" alt="Percona Connect London 2025" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/12/img5_hu_6ae561b777bf29e9.jpeg 480w, https://percona.community/blog/2025/12/img5_hu_5659d491a5fc0e02.jpeg 768w, https://percona.community/blog/2025/12/img5_hu_56250e8f6d310114.jpeg 1400w"
src="https://percona.community/blog/2025/12/img5.jpeg" alt="Percona Connect London 2025" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/12/img7_hu_76f0dbd195c0f33e.jpeg 480w, https://percona.community/blog/2025/12/img7_hu_59b77cc8d2bc6bca.jpeg 768w, https://percona.community/blog/2025/12/img7_hu_fc7b7a2d735e605a.jpeg 1400w"
src="https://percona.community/blog/2025/12/img7.jpeg" alt="Percona Connect London 2025" />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/12/img6_hu_6a3e1a691ef057b0.jpeg 480w, https://percona.community/blog/2025/12/img6_hu_25c14f5bbd6d636d.jpeg 768w, https://percona.community/blog/2025/12/img6_hu_f1c55e1c066f602c.jpeg 1400w"
src="https://percona.community/blog/2025/12/img6.jpeg" alt="Percona Connect London 2025" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_329edaa51c016649.png 480w, https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_a3549555f9e0a365.png 768w, https://percona.community/blog/2025/11/Jan-PG-18-luv_hu_137849b3748f8502.png 1400w"
src="https://percona.community/blog/2025/11/Jan-PG-18-luv.png" alt=" " />&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 src="https://percona.community/blog/2025/11/introm.jpeg" alt="MySQL Operator for MySQL Intro" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/11/two-operators_hu_e3d8f6c73604ca02.png 480w, https://percona.community/blog/2025/11/two-operators_hu_d68c37cdeea667e6.png 768w, https://percona.community/blog/2025/11/two-operators_hu_300c39d76a6bdf0a.png 1400w"
src="https://percona.community/blog/2025/11/two-operators.png" alt="MySQL Operator for MySQL Intro Chart" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_182833534dd8f7b.png 480w, https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_565e9bc65d1675a2.png 768w, https://percona.community/blog/2025/11/mysql_memory_usage_graph_hu_d497e9659ccf47c0.png 1400w"
src="https://percona.community/blog/2025/11/mysql_memory_usage_graph.png" alt="Releem Dashboard - RAM usage" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_ab941316c0d444f0.png 480w, https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_b4753a1fcc563d24.png 768w, https://percona.community/blog/2025/11/mysql_memory_usage_calc_hu_49c89ec803b47b54.png 1400w"
src="https://percona.community/blog/2025/11/mysql_memory_usage_calc.png" alt="MySQL Memory Calculator" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_2e76ea22e4cb632d.png 480w, https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_7f5e70dfeff93692.png 768w, https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics_hu_a17d852be60cc80.png 1400w"
src="https://percona.community/blog/2025/11/mysql_memory_usage_query_analytics.png" alt="Releem Dashboard - Query Analytics" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_36fab27650868ea5.png 480w, https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_538ce99b74935007.png 768w, https://percona.community/blog/2025/10/Jan-TDE-PG18_hu_ebc1ec308aeaf493.png 1400w"
src="https://percona.community/blog/2025/10/Jan-TDE-PG18.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2025/10/intro_hu_c497148933b9f02f.png 480w, https://percona.community/blog/2025/10/intro_hu_bf6434543ed6833a.png 768w, https://percona.community/blog/2025/10/intro_hu_837f2bd377283fe2.png 1400w"
src="https://percona.community/blog/2025/10/intro.png" alt="All Things Open 2021" />&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 src="https://percona.community/blog/2025/10/all.png" alt="All Things Open 2021" />&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 src="https://percona.community/blog/2025/10/kai-wagner.png" alt="All Things Open 2021" />&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 src="https://percona.community/blog/2025/09/pg_tde-docker-version.png" alt="PG_TDE - Docker - Version" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_46fc9f309e0c4218.png 480w, https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_cede0cfbfa063ece.png 768w, https://percona.community/blog/2025/09/pg_tde-pgadmin_hu_aaec6735c457326d.png 1400w"
src="https://percona.community/blog/2025/09/pg_tde-pgadmin.png" alt="PG_TDE - Docker - pgAdmin" />&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 src="https://percona.community/blog/2025/09/pg-tde-key-creation.png" alt="PG_TDE - Docker - Key creation" />&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 src="https://percona.community/blog/2025/09/pg-tde-key-hexdump.png" alt="PG_TDE - Docker - Key file" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_3222e9f0fd0fdeb9.png 480w, https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_7aeb59d3f0a7a203.png 768w, https://percona.community/blog/2025/09/pg-tde-pgadmin-plain_hu_e69a40b59e3cc9e.png 1400w"
src="https://percona.community/blog/2025/09/pg-tde-pgadmin-plain.png" alt="PG_TDE - Docker - Data from the unencrypted table plain_data" />&lt;/figure>&lt;/p>
&lt;p>Data from the encrypted table secure_data:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2025/09/pg-tde-pgadmin-secure.png" alt="PG_TDE - Docker - Data from the unencrypted table plain_data" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/pg-tde-files-plain_hu_37590f60086a33f.png 480w, https://percona.community/blog/2025/09/pg-tde-files-plain_hu_939a706b3d6b37a.png 768w, https://percona.community/blog/2025/09/pg-tde-files-plain_hu_a2c9d75ab70e1f65.png 1400w"
src="https://percona.community/blog/2025/09/pg-tde-files-plain.png" alt="PG_TDE - Docker - the data is readable directly from the file" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/pg-tde-files-secure_hu_ca0ee9800f6ec1bf.png 480w, https://percona.community/blog/2025/09/pg-tde-files-secure_hu_7a7c6164e19fb4a7.png 768w, https://percona.community/blog/2025/09/pg-tde-files-secure_hu_765e6b65b12bc6ea.png 1400w"
src="https://percona.community/blog/2025/09/pg-tde-files-secure.png" alt="PG_TDE - Docker - the contents are fully encrypted" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_6646a34a20c5a1b9.png 480w, https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_d80c548138dc771d.png 768w, https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle_hu_b3d9e57ac1fc0d1f.png 1400w"
src="https://percona.community/blog/2025/09/jan-wal_ga-word_puzzle.png" alt="pg_tde can now encrypt your WAL on PROD!" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/09/jan-wal_ga1_hu_76460d1fc896aca.png 480w, https://percona.community/blog/2025/09/jan-wal_ga1_hu_e66fb3b52d7bb997.png 768w, https://percona.community/blog/2025/09/jan-wal_ga1_hu_b03acc8ec408ced5.png 1400w"
src="https://percona.community/blog/2025/09/jan-wal_ga1.png" alt="pg_tde can now encrypt your WAL on PROD! - 2" />&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 src="https://percona.community/blog/2025/08/jan_feedback_wanted1.png" alt=" " />&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 src="https://percona.community/blog/2025/07/pg-tde-security-audit-small.jpeg" alt="Percona pg_tde: A Security Review Reveals Robust Encryption" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_d9da7980b8ef31d3.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_d1e3781de9c86bc8.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app_hu_d5fb881b1069f790.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-coroot-sync-app.png" alt="GitOps - ArgoCD and Coroot" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_e5a5227526a9ab37.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_6263ca8ac8ccabda.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome_hu_17c070d92b90032a.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-welcome.png" alt="GitOps - ArgoCD and Coroot" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_19c1882d484471cf.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_fa34cad47b7230fc.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard_hu_41013580bea140af.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-home-dashboard.png" alt="GitOps - ArgoCD and Coroot - Home" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_e6e7de6b8f0d14f4.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_a6ae4e8000dffd0e.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load_hu_8dcb75a909611a2d.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-load.png" alt="GitOps - ArgoCD and Coroot - Demo App" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_4d649a08378d2d1a.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_6875265d603fa92d.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster_hu_c144b4e4c765ae76.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster.png" alt="GitOps - ArgoCD and Coroot - PG Cluster" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_50412503e22d3551.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_9f70cf4209e1dfbc.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map_hu_fb80a6a605f0035e.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-map.png" alt="GitOps - ArgoCD and Coroot - PG Cluster" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_2e9cfe6701a6f254.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_23528748fdc62126.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_6d91351cd2ccedd3.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling.png" alt="GitOps - ArgoCD and Coroot - Demo App Profiling" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_2e9cfe6701a6f254.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_23528748fdc62126.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling_hu_6d91351cd2ccedd3.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-demo-profiling.png" alt="GitOps - ArgoCD and Coroot - Demo App Logs" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_d4bb6db723fa13df.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_d753bf1ff228ccdc.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs_hu_5c35d225e551d55f.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-logs.png" alt="GitOps - ArgoCD and Coroot - Demo App Logs" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_2381727e8e34df66.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_1e37d17c340e011.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs_hu_ecfa20d83bd0892.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-costs.png" alt="GitOps - ArgoCD and Coroot - Demo App Costs" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_c5a7d8ed00eca3ab.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_6fd7989d44e7f1da.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage_hu_bf845b4e99005994.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage.png" alt="GitOps - ArgoCD and Coroot - PG Storage" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_d68091fe4936a452.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_ed95998788d09a8.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details_hu_23d67d75d90e808b.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details.png" alt="GitOps - ArgoCD and Coroot - PG Storage Details" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_3ada1633dcf9a61b.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_6161ad5c0039ad47.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result_hu_31967d7a0ec33710.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-details-result.png" alt="GitOps - ArgoCD and Coroot - PG Storage Details Results" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_75b2bce9c4bf908d.png 480w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_ed09977afbd8979a.png 768w, https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result_hu_b39a70f347ba25be.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-coroot-cluster-storage-home-result.png" alt="GitOps - ArgoCD and Coroot - PG Storage Results" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_f7e82c9783a43d91.png 480w, https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_8ecf80945d756a3a.png 768w, https://percona.community/blog/2025/07/gitops-github-argo-demo-app_hu_e5087321ad96287d.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-argo-demo-app.png" alt="GitOps - Percona Operator for Postgres and PG Cluster" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_a5216002601d44a.png 480w, https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_ecc6617f019380e0.png 768w, https://percona.community/blog/2025/07/gitops-demo-app-ui_hu_43937f9d77a63202.png 1400w"
src="https://percona.community/blog/2025/07/gitops-demo-app-ui.png" alt="GitOps - ArgoCD Demo App UI" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_af293f7503ec6a79.png 480w, https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_44e6451a426458aa.png 768w, https://percona.community/blog/2025/07/gitops-demo-app-ui-connect_hu_d38c2f7dd717129a.png 1400w"
src="https://percona.community/blog/2025/07/gitops-demo-app-ui-connect.png" alt="GitOps - ArgoCD Demo App UI - Connect" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_5517ec3b89a13b26.png 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_132730d99fb3819b.png 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-new-user_hu_799bfdf42374359f.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-pg-new-user.png" alt="GitOps - ArgoCD Demo App UI" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_e28b8a3338806aa3.png 480w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_2f0a7cf452382a41.png 768w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user_hu_d7f241e5df838cdf.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocs-demo-app-new-user.png" alt="GitOps - ArgoCD Demo App UI - Connection" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_b05be9189ce8a29c.png 480w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_feaa2ca204a6ed74.png 768w, https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset_hu_bd097498f5e03155.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocs-demo-app-dataset.png" alt="GitOps - ArgoCD Demo App UI - Connection" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_8e0c4a5db276fb85.png 480w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_52432bdb768937d6.png 768w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load_hu_a82212f4c1e5503a.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-demo-app-enable-load.png" alt="GitOps - ArgoCD Demo App UI - Enable Load" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_8f57f1fa04e0ebdf.png 480w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_7a50876dac856935.png 768w, https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel_hu_255f70a0ce31e068.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-demo-app-panel.png" alt="GitOps - ArgoCD Demo App UI - Load Generator" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-pg-init_hu_403a638106827333.png 480w, https://percona.community/blog/2025/07/gitops-github-pg-init_hu_dc21e446152715f0.png 768w, https://percona.community/blog/2025/07/gitops-github-pg-init_hu_da3654d96e1f48a4.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-pg-init.png" alt="GitOps - Percona Operator for Postgres and PG Cluster" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_746089d5ff24eebb.png 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_51470eb9b162b0a8.png 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync_hu_1dee0f6843cceb4a.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-pg-app-sync.png" alt="GitOps - ArgoCD app - Postgres" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_b364f85c56a6b115.png 480w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_8161680482781052.png 768w, https://percona.community/blog/2025/07/gitops-argocd-pg-app-map_hu_9d188ec511f1c786.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-pg-app-map.png" alt="GitOps - ArgoCD app - Postgres - map" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-scale_hu_8b1f2c5c1536e848.png 480w, https://percona.community/blog/2025/07/gitops-github-scale_hu_acf35b98026c2aa0.png 768w, https://percona.community/blog/2025/07/gitops-github-scale_hu_b0dc2eb9f46961e0.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-scale.png" alt="GitOps - ArgoCD app - Postgres scale - GitHub" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_3a0e618bada99ba6.png 480w, https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_d5618d1ba625e2d7.png 768w, https://percona.community/blog/2025/07/gitops-github-scale-argo_hu_be9d97706a13a583.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-scale-argo.png" alt="GitOps - ArgoCD apps - Postgres scale - Argo" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-login_hu_2b8e3a18d84ec798.png 480w, https://percona.community/blog/2025/07/gitops-argocd-login_hu_8c25a58ba7500700.png 768w, https://percona.community/blog/2025/07/gitops-argocd-login_hu_e3a7c94ed685b7da.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-login.png" alt="GitOps - ArgoCD UI" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_23c44b0339a87aa8.png 480w, https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_654e20efe0754008.png 768w, https://percona.community/blog/2025/07/gitops-argocd-dashboard_hu_f0fa19e2c0e740a7.png 1400w"
src="https://percona.community/blog/2025/07/gitops-argocd-dashboard.png" alt="GitOps: ArgoCD web UI" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-new-repo_hu_c1092ea410e1d482.png 480w, https://percona.community/blog/2025/07/gitops-github-new-repo_hu_2d70c7b49bae26ee.png 768w, https://percona.community/blog/2025/07/gitops-github-new-repo_hu_cb106e4582611ded.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-new-repo.png" alt="GitOps: GitHub Repo Creation" />&lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>Clone the repo:&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/07/gitops-github-clone_hu_82856ab44db43d16.png 480w, https://percona.community/blog/2025/07/gitops-github-clone_hu_7aa24a16af6a42c2.png 768w, https://percona.community/blog/2025/07/gitops-github-clone_hu_3e1e4f2077b9bd3c.png 1400w"
src="https://percona.community/blog/2025/07/gitops-github-clone.png" alt="GitOps: GitHub Clone" />&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 src="https://percona.community/blog/2025/07/ivan_cover.png" alt="Using_replicaSetHorizons_in_MongoDB" />&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 src="https://percona.community/blog/2025/07/PG_conflict.png" alt="PG Conflict" />&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 src="https://percona.community/blog/2025/07/PG_chili.png" alt="PG Chili" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/06/geeks-blog-1_hu_267ea92f3b8bca36.jpg 480w, https://percona.community/blog/2025/06/geeks-blog-1_hu_a7ac3829dc3dc78e.jpg 768w, https://percona.community/blog/2025/06/geeks-blog-1_hu_55e76bf9521d4bac.jpg 1400w"
src="https://percona.community/blog/2025/06/geeks-blog-1.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-2.jpg" alt="Geeks Go Peaks" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/06/geeks-blog-3_hu_dab6a1d35cac4484.jpg 480w, https://percona.community/blog/2025/06/geeks-blog-3_hu_eb418e7a25cb884d.jpg 768w, https://percona.community/blog/2025/06/geeks-blog-3_hu_4a6489e2d48ef40f.jpg 1400w"
src="https://percona.community/blog/2025/06/geeks-blog-3.jpg" alt="Geeks Go Peaks" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/06/geeks-blog-4_hu_3dd67438fd888a7e.jpg 480w, https://percona.community/blog/2025/06/geeks-blog-4_hu_c8569d99f343bc9b.jpg 768w, https://percona.community/blog/2025/06/geeks-blog-4_hu_1761ea98d33e5f66.jpg 1400w"
src="https://percona.community/blog/2025/06/geeks-blog-4.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-5.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-6.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-7.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-8.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/geeks-blog-9.jpg" alt="Geeks Go Peaks" />&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 src="https://percona.community/blog/2025/06/twitter1.jpeg" alt="Twitter" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/05/pg18img_hu_9976bc51805a49cb.png 480w, https://percona.community/blog/2025/05/pg18img_hu_31fc038739fc52b8.png 768w, https://percona.community/blog/2025/05/pg18img_hu_c86e80e79914b07a.png 1400w"
src="https://percona.community/blog/2025/05/pg18img.png" alt="Postgres 18 is coming!" />&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 src="https://percona.community/blog/2025/05/nightly_builds.jpg" alt="Progress report on pg_tde - nightly builds" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_8e877e10d43414f4.png 480w, https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_4f9758ca9365ee98.png 768w, https://percona.community/blog/2025/03/pmm-mongodb-rs_hu_42d0c424fcd5e4b8.png 1400w"
src="https://percona.community/blog/2025/03/pmm-mongodb-rs.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Dashboard - MongoDB Replica Set Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/docker-pmm-start_hu_3909e5a4671f6495.png 480w, https://percona.community/blog/2025/03/docker-pmm-start_hu_898ccea59f593e2b.png 768w, https://percona.community/blog/2025/03/docker-pmm-start_hu_ad0659857f814903.png 1400w"
src="https://percona.community/blog/2025/03/docker-pmm-start.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop Start" />&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 src="https://percona.community/blog/2025/03/pmm-login.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Login" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2025/03/pmm-home_hu_6f0a507496133b77.png 480w, https://percona.community/blog/2025/03/pmm-home_hu_1406d5f6bdc98fc6.png 768w, https://percona.community/blog/2025/03/pmm-home_hu_6568e254d94ccb86.png 1400w"
src="https://percona.community/blog/2025/03/pmm-home.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Home Page" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_bb9842f0502a5d59.png 480w, https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_171053abe6d42b2.png 768w, https://percona.community/blog/2025/03/docker-pmm-mongodb_hu_33937ddce310b985.png 1400w"
src="https://percona.community/blog/2025/03/docker-pmm-mongodb.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/pmm-home-mongodb_hu_b6fcfc17c39e35b2.png 480w, https://percona.community/blog/2025/03/pmm-home-mongodb_hu_45fe2e0cc9332840.png 768w, https://percona.community/blog/2025/03/pmm-home-mongodb_hu_7c396f8ba7d7098.png 1400w"
src="https://percona.community/blog/2025/03/pmm-home-mongodb.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/docker-desktop-rs_hu_c171901a96bf081a.png 480w, https://percona.community/blog/2025/03/docker-desktop-rs_hu_efaca3ac6ebcd41b.png 768w, https://percona.community/blog/2025/03/docker-desktop-rs_hu_1b33539b6c1a7e52.png 1400w"
src="https://percona.community/blog/2025/03/docker-desktop-rs.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/pmm-rs-services_hu_d0ad375de0dc8708.png 480w, https://percona.community/blog/2025/03/pmm-rs-services_hu_362f4b685a1025ac.png 768w, https://percona.community/blog/2025/03/pmm-rs-services_hu_447ef5090f685c71.png 1400w"
src="https://percona.community/blog/2025/03/pmm-rs-services.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/pmm-rs-statuses_hu_152c5a5848d218c0.png 480w, https://percona.community/blog/2025/03/pmm-rs-statuses_hu_a41e7ee71109cd35.png 768w, https://percona.community/blog/2025/03/pmm-rs-statuses_hu_9c7cef99e95ae73e.png 1400w"
src="https://percona.community/blog/2025/03/pmm-rs-statuses.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - PMM MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/03/mongodb-connect_hu_1993f455a7ac9c0d.png 480w, https://percona.community/blog/2025/03/mongodb-connect_hu_d494614723a4a044.png 768w, https://percona.community/blog/2025/03/mongodb-connect_hu_c87f2510dcd2bf5e.png 1400w"
src="https://percona.community/blog/2025/03/mongodb-connect.png" alt="Percona Monitoring and Management (PMM) 3.0.0 - Docker Desktop MongoDB Connect" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/02/pmm3-homepage_hu_ab475622fd130cae.jpg 480w, https://percona.community/blog/2025/02/pmm3-homepage_hu_b786868d12aa39e8.jpg 768w, https://percona.community/blog/2025/02/pmm3-homepage_hu_256f4d15d640605c.jpg 1400w"
src="https://percona.community/blog/2025/02/pmm3-homepage.jpg" alt="Percona Monitoring and Management (PMM) 3.0.0" />&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 src="https://percona.community/blog/2025/01/operator-mongodb-8.png" alt="Percona Server for MongoDB 8.0 Support" />&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 sizes="100vw" srcset="https://percona.community/blog/2025/01/PMM-3.0.0_hu_bab4bf52b037997b.png 480w, https://percona.community/blog/2025/01/PMM-3.0.0_hu_aad2d5b4f9ec2c44.png 768w, https://percona.community/blog/2025/01/PMM-3.0.0_hu_a72c59d901d4239b.png 1400w"
src="https://percona.community/blog/2025/01/PMM-3.0.0.png" alt="Percona Monitoring and Management (PMM) 3.0.0" />&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/images/pmm/pmm-blog-post-cover_hu_d535f2202891bf3f.jpg"/><media:content url="https://percona.community/images/pmm/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 src="https://percona.community/blog/2024/07/databases-kubernetes-timeline.png" alt="Databases Kubernetes Timeline" />&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 src="https://percona.community/blog/2024/07/percona-everest.png" alt="Percona Everest" />&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 src="https://percona.community/blog/2024/06/coc-talk.png" alt="Community Over Code Talk" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/coc-community_hu_40334573b85eb2a0.png 480w, https://percona.community/blog/2024/06/coc-community_hu_5799465dd1dcf162.png 768w, https://percona.community/blog/2024/06/coc-community_hu_82a3f21768d271f6.png 1400w"
src="https://percona.community/blog/2024/06/coc-community.png" alt="Community Over Code Slide" />&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 src="https://percona.community/blog/2024/06/coc-percona.png" alt="Work At Percona" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/coc-percona-everest_hu_340fb379522d6ef1.png 480w, https://percona.community/blog/2024/06/coc-percona-everest_hu_f443fb3b5db8747d.png 768w, https://percona.community/blog/2024/06/coc-percona-everest_hu_e397a6e56e8bcf48.png 1400w"
src="https://percona.community/blog/2024/06/coc-percona-everest.png" alt="Percona Everest" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_6ae705806e7101b3.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_e59dcb21cd3e3c50.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-github_hu_7738417e3944ae3d.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-github.png" alt="Percona Everest 1.0.0-RC1 GitHub" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_43e3ad7581b7e60c.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_71237ed3cca19982.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-install_hu_f3f8e58de434f9bf.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-install.png" alt="Percona Everest 1.0.0-RC1 Install" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_281d9fa40252f230.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_c239019808a71a10.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass_hu_a97f68e66de27083.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-admin-pass.png" alt="Percona Everest 1.0.0-RC1 Admin Password" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_2afb08041fc4dd1f.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_27c8e86961a74ed5.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-port_hu_eb8c9c3656902b45.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-port.png" alt="Percona Everest 1.0.0-RC1 Port Forward" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_a4abed4e8073e91c.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_dcbee8c9f8d91988.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-login_hu_4dcb2cb7c2e81ee9.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-login.png" alt="Percona Everest 1.0.0-RC1 User Authentication" />&lt;/figure>&lt;/p>
&lt;p>Create a PostgreSQL cluster to test how it works.&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_e06f9c0209f0d0e2.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_68e03db4623dd0cf.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-db_hu_fc772e769205e9c2.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-db.png" alt="Percona Everest 1.0.0-RC1 Create PostgreSQL" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_ff54650567559751.png 480w, https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_945cf22260bc62cb.png 768w, https://percona.community/blog/2024/06/percona-everest-1-rc-postgres_hu_5cb256eb92f073f5.png 1400w"
src="https://percona.community/blog/2024/06/percona-everest-1-rc-postgres.png" alt="Percona Everest 1.0.0-RC1 PostgreSQL" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/clone-plugin-img1_hu_92b7fba6627fff9c.png 480w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_c39e39890752fd7d.png 768w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_fb34b4287d629b8d.png 1400w"
src="https://percona.community/blog/2024/06/clone-plugin-img1.png" alt="clone image 1" />&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 src="https://percona.community/blog/2024/06/clone-plugin-img2.png" alt="clone image 2" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/06/clone-plugin-img1_hu_92b7fba6627fff9c.png 480w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_c39e39890752fd7d.png 768w, https://percona.community/blog/2024/06/clone-plugin-img1_hu_fb34b4287d629b8d.png 1400w"
src="https://percona.community/blog/2024/06/clone-plugin-img1.png" alt="clone image 1" />&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 src="https://percona.community/blog/2024/06/clone-plugin-img3.png" alt="clone image 3" />&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 src="https://percona.community/blog/2024/05/image2.jpg" alt="Learn SQL. Learn PostgreSQL." />&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 src="https://percona.community/blog/2024/05/image4.jpg" alt="Learn SQL. First Exercise 1" />&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 src="https://percona.community/blog/2024/05/image3.jpg" alt="Learn SQL. First Exercise 2" />&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 src="https://percona.community/blog/2024/05/image1.jpg" alt="Learn SQL. First Exercise 3" />&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 src="https://percona.community/blog/2024/05/image5.jpg" alt="Learn SQL. First Exercise 4" />&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 src="https://percona.community/blog/2024/05/peterdiogo.png" alt="Percona Everest Webinar" />&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 src="https://percona.community/blog/2024/05/percona-everest-mongodb.png" alt="Percona Everest Draw" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/05/everest-gui_hu_b50d53946abc8f4e.png 480w, https://percona.community/blog/2024/05/everest-gui_hu_84f30b9b15bb341f.png 768w, https://percona.community/blog/2024/05/everest-gui_hu_7b6b85bd53e63366.png 1400w"
src="https://percona.community/blog/2024/05/everest-gui.png" alt="Percona Everest GUI" />&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 src="https://percona.community/blog/2024/04/percona-everest.png" alt="Percona Everest" />&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 src="https://percona.community/blog/2024/04/pe-installing-wsl.jpeg" alt="WSL Installing" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-installed-wsl_hu_4b740a4f62a42b8f.jpeg 480w, https://percona.community/blog/2024/04/pe-installed-wsl_hu_2664582b4817e57a.jpeg 768w, https://percona.community/blog/2024/04/pe-installed-wsl_hu_e14abe8fbc89622.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-installed-wsl.jpeg" alt="WSL Installed" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-login_hu_29047cff5f60bbd2.jpeg 480w, https://percona.community/blog/2024/04/pe-login_hu_ebb958b9073ed806.jpeg 768w, https://percona.community/blog/2024/04/pe-login_hu_b324660a7bbdbefd.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-login.jpeg" alt="Percona Everest Login" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-first_hu_63c5453d2c5a899d.jpeg 480w, https://percona.community/blog/2024/04/pe-first_hu_e09ef42791edea67.jpeg 768w, https://percona.community/blog/2024/04/pe-first_hu_56c8ec7eeaeee088.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-first.jpeg" alt="Percona Everest Create Database" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-second_hu_51afce8ecdc942e2.jpeg 480w, https://percona.community/blog/2024/04/pe-second_hu_f85e54b021f5adae.jpeg 768w, https://percona.community/blog/2024/04/pe-second_hu_3b8030f8ddd35cbc.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-second.jpeg" alt="Percona Everest Databases" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-third_hu_f7b06c2ed3483a23.jpeg 480w, https://percona.community/blog/2024/04/pe-third_hu_d7b45f4a6304fc4a.jpeg 768w, https://percona.community/blog/2024/04/pe-third_hu_9ba47a8cdf981488.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-third.jpeg" alt="Percona Everest Screen" />&lt;/figure>
And this is how it looks: your database is in Kubernetes!&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2024/04/pe-last_hu_e09d67f2bac6f061.jpeg 480w, https://percona.community/blog/2024/04/pe-last_hu_ef2e806133f82acb.jpeg 768w, https://percona.community/blog/2024/04/pe-last_hu_b6fb5bc3d2e6da45.jpeg 1400w"
src="https://percona.community/blog/2024/04/pe-last.jpeg" alt="Percona Everest Details" />&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 src="https://percona.community/blog/2024/03/standby.png" alt="Percona Demo for StandBy Cluster" />&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 src="https://percona.community/blog/2024/01/k8s-mandala-medium.png" alt="Mandala" />&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 sizes="100vw" srcset="https://percona.community/blog/2024/01/dok-initiatives_hu_4bcb895cefa55e4d.png 480w, https://percona.community/blog/2024/01/dok-initiatives_hu_7f5c00b0f4b03204.png 768w, https://percona.community/blog/2024/01/dok-initiatives_hu_3ea9e18a22740b26.png 1400w"
src="https://percona.community/blog/2024/01/dok-initiatives.png" alt="DoKC Initiatives" />&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 src="https://percona.community/blog/2024/02/dok2.png" alt="DoKC agenda" />&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 src="https://percona.community/blog/2024/02/dok3.png" alt="DoKC badge" />&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 src="https://percona.community/blog/2023/11/affinity-01.png" alt="Affinity" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/11/affinity-02_hu_9f71c4f3e029f3f6.png 480w, https://percona.community/blog/2023/11/affinity-02_hu_f22f9a32d98909e3.png 768w, https://percona.community/blog/2023/11/affinity-02_hu_5830a796c8ada964.png 1400w"
src="https://percona.community/blog/2023/11/affinity-02.png" alt="Affinity" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/11/day2_hu_8d3b899e02dcbc.png 480w, https://percona.community/blog/2023/11/day2_hu_60931a122356f562.png 768w, https://percona.community/blog/2023/11/day2_hu_9a82e534692d6bbc.png 1400w"
src="https://percona.community/blog/2023/11/day2.png" alt="day02" />&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 src="https://percona.community/blog/2023/11/pmm.png" alt="pmm" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/11/steteless-and-stateful_hu_d4dca7f1ecb66dcd.png 480w, https://percona.community/blog/2023/11/steteless-and-stateful_hu_60e3c79bfb338538.png 768w, https://percona.community/blog/2023/11/steteless-and-stateful_hu_bf0a78cdbbceecff.png 1400w"
src="https://percona.community/blog/2023/11/steteless-and-stateful.png" alt="steteless-and-stateful" />&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 src="https://percona.community/blog/2023/11/dok.png" alt="dok" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/11/k8s-01_hu_e56aaa8dbc6d4a1.png 480w, https://percona.community/blog/2023/11/k8s-01_hu_bb3ea29d96e519d2.png 768w, https://percona.community/blog/2023/11/k8s-01_hu_fa5fccab6c97cf60.png 1400w"
src="https://percona.community/blog/2023/11/k8s-01.png" alt="kubernetes-operators" />&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 src="https://percona.community/blog/2023/11/k8s-02.png" alt="kubernetes-operators" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-frontend_hu_68b14b415811cb5f.png 480w, https://percona.community/blog/2023/10/everest-frontend_hu_424f64337a05a1ad.png 768w, https://percona.community/blog/2023/10/everest-frontend_hu_7ad91859b8444a16.png 1400w"
src="https://percona.community/blog/2023/10/everest-frontend.png" alt="Percona Everest Frontend" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-front-run_hu_aa37abd3d7484a40.png 480w, https://percona.community/blog/2023/10/everest-front-run_hu_a370f3cbf540ff96.png 768w, https://percona.community/blog/2023/10/everest-front-run_hu_3f9e560d07d7cdcd.png 1400w"
src="https://percona.community/blog/2023/10/everest-front-run.png" alt="Percona Everest Frontend Run" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-front-run-result_hu_17d3378a57f0175f.png 480w, https://percona.community/blog/2023/10/everest-front-run-result_hu_688777866a4175f5.png 768w, https://percona.community/blog/2023/10/everest-front-run-result_hu_111db07964af1301.png 1400w"
src="https://percona.community/blog/2023/10/everest-front-run-result.png" alt="Percona Everest Frontend Result" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/backend-docker-pg_hu_6ae47758e8022e26.png 480w, https://percona.community/blog/2023/10/backend-docker-pg_hu_232da1c9da0ee991.png 768w, https://percona.community/blog/2023/10/backend-docker-pg_hu_de306eb13379f6fe.png 1400w"
src="https://percona.community/blog/2023/10/backend-docker-pg.png" alt="Percona Everest Backend" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_b27590def9fdf2a8.png 480w, https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_938fe5c5c95eb98b.png 768w, https://percona.community/blog/2023/10/backend-docker-pg-desktop_hu_7578521bab7d3420.png 1400w"
src="https://percona.community/blog/2023/10/backend-docker-pg-desktop.png" alt="Percona Everest Backend" />&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 src="https://percona.community/blog/2023/10/backend-go-run.png" alt="Percona Everest Backend Go Run" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/backend-go-run-result_hu_3b8a07d031fb452d.png 480w, https://percona.community/blog/2023/10/backend-go-run-result_hu_e3aa160714bb3f48.png 768w, https://percona.community/blog/2023/10/backend-go-run-result_hu_2b2f784c46c5079.png 1400w"
src="https://percona.community/blog/2023/10/backend-go-run-result.png" alt="Percona Everest Backend Go Result" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-minikube_hu_19e62a166c161ceb.png 480w, https://percona.community/blog/2023/10/everest-minikube_hu_36d59f467a0af4e9.png 768w, https://percona.community/blog/2023/10/everest-minikube_hu_cc29da85142e255f.png 1400w"
src="https://percona.community/blog/2023/10/everest-minikube.png" alt="Percona Everest Backend Minikube" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-wizards_hu_a904831db6cc2923.png 480w, https://percona.community/blog/2023/10/everest-wizards_hu_f0cbde7b620730b4.png 768w, https://percona.community/blog/2023/10/everest-wizards_hu_ab37b446973d79a1.png 1400w"
src="https://percona.community/blog/2023/10/everest-wizards.png" alt="Percona Everest Wizard" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-finish-start_hu_906bcca62f6d5dec.png 480w, https://percona.community/blog/2023/10/everest-finish-start_hu_b003b5e66c04e34d.png 768w, https://percona.community/blog/2023/10/everest-finish-start_hu_efa0208f4c53c3.png 1400w"
src="https://percona.community/blog/2023/10/everest-finish-start.png" alt="Percona Everest Start" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-create-db_hu_f50e69a221bac503.png 480w, https://percona.community/blog/2023/10/everest-create-db_hu_8e1f19bf1d420a5a.png 768w, https://percona.community/blog/2023/10/everest-create-db_hu_1fcc40e7ebfee3cf.png 1400w"
src="https://percona.community/blog/2023/10/everest-create-db.png" alt="Percona Everest Create DB" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/everest-dbs_hu_6d0c46e8e381c830.png 480w, https://percona.community/blog/2023/10/everest-dbs_hu_d10fbd6316d4d763.png 768w, https://percona.community/blog/2023/10/everest-dbs_hu_4a62d6fdf8ccb0ed.png 1400w"
src="https://percona.community/blog/2023/10/everest-dbs.png" alt="Percona Everest Create DB" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/10/kcduk-01_hu_bd6faf10c7fc4fca.jpg 480w, https://percona.community/blog/2023/10/kcduk-01_hu_aa53236b59c4188b.jpg 768w, https://percona.community/blog/2023/10/kcduk-01_hu_13ae809b73043ef5.jpg 1400w"
src="https://percona.community/blog/2023/10/kcduk-01.jpg" alt="lizrice-keynote-01" />&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 src="https://percona.community/blog/2023/10/kcduk-02.png" alt="addfea-to-the-kernel" />&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 src="https://percona.community/blog/2023/10/kcduk-03.png" alt="addingfeatures-to-the-kernel-with-ebpf" />&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 src="https://percona.community/blog/2023/10/kcduk-04.png" alt="cncf-project-maturity-levels" />&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 src="https://percona.community/blog/2023/09/opensource-01.jpeg" alt="image" />&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 src="https://percona.community/blog/2023/09/opensource-02.png" alt="image" />&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 src="https://percona.community/blog/2023/09/opensource-03.png" alt="image" />&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 src="https://percona.community/blog/2023/08/dolphie-150.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_24be22e07edc4bf4.png 480w, https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_df05cb1e26fb3c6b.png 768w, https://percona.community/blog/2023/08/dolphie_dashboard_processlist_hu_92b0f1475efd055e.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_dashboard_processlist.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_fdce19cbdc265c45.png 480w, https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_988946439dee2f04.png 768w, https://percona.community/blog/2023/08/dolphie_buffer_pool_hu_62e296398fe406a0.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_buffer_pool.png" alt="image" />&lt;/figure>&lt;/p>
&lt;h4 id="checkpoint-graph">Checkpoint Graph&lt;/h4>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_checkpoint_hu_5033305ee10d6583.png 480w, https://percona.community/blog/2023/08/dolphie_checkpoint_hu_74a6e4c86ec95af9.png 768w, https://percona.community/blog/2023/08/dolphie_checkpoint_hu_5a698e4c73d34dcb.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_checkpoint.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_redo_log_hu_9910b81fe35285ca.png 480w, https://percona.community/blog/2023/08/dolphie_redo_log_hu_6b9afd6973d0a0db.png 768w, https://percona.community/blog/2023/08/dolphie_redo_log_hu_72f024a19a7ff856.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_redo_log.png" alt="image" />&lt;/figure>&lt;/p>
&lt;h4 id="dml-graph">DML Graph&lt;/h4>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_dml_hu_75d0ff8eec2097c8.png 480w, https://percona.community/blog/2023/08/dolphie_dml_hu_994150f7053e54e3.png 768w, https://percona.community/blog/2023/08/dolphie_dml_hu_e93fcf1a3f10c47b.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_dml.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_thread_details_hu_8f8c1fb75755d18c.png 480w, https://percona.community/blog/2023/08/dolphie_thread_details_hu_b496012bf78e57c4.png 768w, https://percona.community/blog/2023/08/dolphie_thread_details_hu_63f9f4bd3b0b4058.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_thread_details.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_c4f31ce433898534.png 480w, https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_964130d68a4a7dfb.png 768w, https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters_hu_96107756d31feeb6.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_kill_threads_by_parameters.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_5d3c6a08487285f0.png 480w, https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_169ea628728d9861.png 768w, https://percona.community/blog/2023/08/dolphie_quick_host_switch_hu_da537a303647e77c.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_quick_host_switch.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dolphie_error_log_hu_29ea1ccb044dab49.png 480w, https://percona.community/blog/2023/08/dolphie_error_log_hu_5a3b5f1623d4935c.png 768w, https://percona.community/blog/2023/08/dolphie_error_log_hu_5cc6d6bdaa55ba72.png 1400w"
src="https://percona.community/blog/2023/08/dolphie_error_log.png" alt="image" />&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 src="https://percona.community/blog/2023/08/dolphie_errant_transaction.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/node-summary_hu_d2c3c656f60a8a9f.png 480w, https://percona.community/blog/2023/08/node-summary_hu_92a27a6cfcf3cff1.png 768w, https://percona.community/blog/2023/08/node-summary_hu_48671787c34a28c9.png 1400w"
src="https://percona.community/blog/2023/08/node-summary.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/pmm-view2_hu_f272ad7e1c2e61e8.png 480w, https://percona.community/blog/2023/08/pmm-view2_hu_87b4f8ea44657c53.png 768w, https://percona.community/blog/2023/08/pmm-view2_hu_1e0b1a2173273e7d.png 1400w"
src="https://percona.community/blog/2023/08/pmm-view2.png" alt="image" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/dok-game_hu_ad464c34addcee32.png 480w, https://percona.community/blog/2023/08/dok-game_hu_3dcb72086a2fa28.png 768w, https://percona.community/blog/2023/08/dok-game_hu_4b488f857233f4db.png 1400w"
src="https://percona.community/blog/2023/08/dok-game.png" alt="dokc-game" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/kafka-spark_hu_1827450969142890.png 480w, https://percona.community/blog/2023/08/kafka-spark_hu_93948a3fd66ce4d8.png 768w, https://percona.community/blog/2023/08/kafka-spark_hu_90a5f03aee3edfbe.png 1400w"
src="https://percona.community/blog/2023/08/kafka-spark.png" alt="dokc-game" />&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 src="https://percona.community/blog/2023/08/dataconla.png" alt="dokc-game" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/08/argo_hu_9b2e75bcd0723565.png 480w, https://percona.community/blog/2023/08/argo_hu_57ea539f04d83654.png 768w, https://percona.community/blog/2023/08/argo_hu_2525015a282057a8.png 1400w"
src="https://percona.community/blog/2023/08/argo.png" alt="dokc-game" />&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 src="https://percona.community/blog/2023/06/pup-stikers-01.jpeg" alt="pup-stikers-01" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-peter-02_hu_9ffb46fbb29c64ac.jpeg 480w, https://percona.community/blog/2023/06/pup-peter-02_hu_20b372dfb682ee8a.jpeg 768w, https://percona.community/blog/2023/06/pup-peter-02_hu_2223c556753348c4.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-peter-02.jpeg" alt="pup-peter-02" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-michael-03_hu_6d2ca38bdd96eb82.jpeg 480w, https://percona.community/blog/2023/06/pup-michael-03_hu_f3e0fd56b9697070.jpeg 768w, https://percona.community/blog/2023/06/pup-michael-03_hu_8f47b9cad5420bc0.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-michael-03.jpeg" alt="pup-michael-03" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-breakfast-04_hu_4a7a74c79cedf854.jpeg 480w, https://percona.community/blog/2023/06/pup-breakfast-04_hu_184a0148ed7d4042.jpeg 768w, https://percona.community/blog/2023/06/pup-breakfast-04_hu_2c4055d61bd924e5.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-breakfast-04.jpeg" alt="pup-breakfast-04" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-peter-05_hu_fefc2436b57d5f82.jpeg 480w, https://percona.community/blog/2023/06/pup-peter-05_hu_5895ad050b152d.jpeg 768w, https://percona.community/blog/2023/06/pup-peter-05_hu_3517c8432dd7766e.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-peter-05.jpeg" alt="pup-peter-05" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-edith-06_hu_f2bb8901a1843d9d.jpeg 480w, https://percona.community/blog/2023/06/pup-edith-06_hu_f305f7e3ba90b609.jpeg 768w, https://percona.community/blog/2023/06/pup-edith-06_hu_ecf04830c9d69e95.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-edith-06.jpeg" alt="pup-edith-06" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-peter-07_hu_13b4389dcec0ce0e.jpeg 480w, https://percona.community/blog/2023/06/pup-peter-07_hu_d635423cdd48e59.jpeg 768w, https://percona.community/blog/2023/06/pup-peter-07_hu_d8bfb6f5dc2c53d4.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-peter-07.jpeg" alt="pup-peter-07" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-team-08_hu_4afbf356772a0a8d.jpeg 480w, https://percona.community/blog/2023/06/pup-team-08_hu_144a2184947ca643.jpeg 768w, https://percona.community/blog/2023/06/pup-team-08_hu_9559ed30d6102448.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-team-08.jpeg" alt="pup-team-08" />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-public-09_hu_ff65e456e954f1d1.jpeg 480w, https://percona.community/blog/2023/06/pup-public-09_hu_d7819a2af8585871.jpeg 768w, https://percona.community/blog/2023/06/pup-public-09_hu_f625fc05749c0336.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-public-09.jpeg" alt="pup-public-09" />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-all-10_hu_528cb9e19c574754.jpeg 480w, https://percona.community/blog/2023/06/pup-all-10_hu_9f19bd400c95e0f9.jpeg 768w, https://percona.community/blog/2023/06/pup-all-10_hu_3593470cd690b953.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-all-10.jpeg" alt="pup-all-10" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-lunch-11_hu_e9236907e399530d.jpeg 480w, https://percona.community/blog/2023/06/pup-lunch-11_hu_bcf683a07ce6cdfa.jpeg 768w, https://percona.community/blog/2023/06/pup-lunch-11_hu_c0c90cbd3ad1344e.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-lunch-11.jpeg" alt="pup-lunch-11" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/pup-team-12_hu_13fe475e57a3882e.jpeg 480w, https://percona.community/blog/2023/06/pup-team-12_hu_a882affa6ee40274.jpeg 768w, https://percona.community/blog/2023/06/pup-team-12_hu_3ab57b75a01d11c0.jpeg 1400w"
src="https://percona.community/blog/2023/06/pup-team-12.jpeg" alt="pup-team-12" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/dok-datainfra_hu_7b0c7765b89587f3.jpeg 480w, https://percona.community/blog/2023/06/dok-datainfra_hu_341b3d3b21b105f5.jpeg 768w, https://percona.community/blog/2023/06/dok-datainfra_hu_107d85156bd1ca95.jpeg 1400w"
src="https://percona.community/blog/2023/06/dok-datainfra.jpeg" alt="Panel Discussion" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/06/dok-security-hardering_hu_3f51e5babc38029f.jpeg 480w, https://percona.community/blog/2023/06/dok-security-hardering_hu_2a38fc6f49ac0f71.jpeg 768w, https://percona.community/blog/2023/06/dok-security-hardering_hu_95fe32da725a235a.jpeg 1400w"
src="https://percona.community/blog/2023/06/dok-security-hardering.jpeg" alt="Panel Discussion" />&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 src="https://percona.community/blog/2023/05/01-pd-intro.jpeg" alt="Panel Discussion" />&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 src="https://percona.community/blog/2023/05/02-pd-panel-discution.jpeg" alt="Panel Discussion" />&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 src="https://percona.community/blog/2023/05/03-pd-capability-models.jpeg" alt="Panel Discussion" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/contribution2_hu_94d1252e7873a71a.jpg 480w, https://percona.community/blog/2023/05/contribution2_hu_77c7ef72837f51a7.jpg 768w, https://percona.community/blog/2023/05/contribution2_hu_3031673fbd200a2b.jpg 1400w"
src="https://percona.community/blog/2023/05/contribution2.jpg" alt="Contribution" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/contribution1_hu_c45b801f2d40d62c.jpg 480w, https://percona.community/blog/2023/05/contribution1_hu_f71352ebd639c2cb.jpg 768w, https://percona.community/blog/2023/05/contribution1_hu_f589238117718bb0.jpg 1400w"
src="https://percona.community/blog/2023/05/contribution1.jpg" alt="Contribution" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/contribution3_hu_56b6b21ef294a916.jpg 480w, https://percona.community/blog/2023/05/contribution3_hu_190d31d23ea9722e.jpg 768w, https://percona.community/blog/2023/05/contribution3_hu_e84d49da02568d17.jpg 1400w"
src="https://percona.community/blog/2023/05/contribution3.jpg" alt="Contribution" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/01-percona-kubecon_hu_c1ca5fc462feb3ca.jpg 480w, https://percona.community/blog/2023/05/01-percona-kubecon_hu_84ba9093843a0bf7.jpg 768w, https://percona.community/blog/2023/05/01-percona-kubecon_hu_3fd2fbf0a8ed07bb.jpg 1400w"
src="https://percona.community/blog/2023/05/01-percona-kubecon.jpg" alt="Percona At Kubecon" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/02-percona-team_hu_6a1b9d4a83eb6b5f.JPG 480w, https://percona.community/blog/2023/05/02-percona-team_hu_9001b6a047c40d27.JPG 768w, https://percona.community/blog/2023/05/02-percona-team_hu_145c32af2f2c18a4.JPG 1400w"
src="https://percona.community/blog/2023/05/02-percona-team.JPG" alt="Percona Team" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_afb248e4c85d7a87.jpeg 480w, https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_cce5ceb33f1eab7e.jpeg 768w, https://percona.community/blog/2023/05/03-kubecon-happyhour_hu_640e67a17e3019b3.jpeg 1400w"
src="https://percona.community/blog/2023/05/03-kubecon-happyhour.jpeg" alt="Kubecon Happy Hour" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/04-bicicle_hu_cf11f52540367ec6.jpg 480w, https://percona.community/blog/2023/05/04-bicicle_hu_86d206f37bc97a7d.jpg 768w, https://percona.community/blog/2023/05/04-bicicle_hu_951c1fa69f4379d8.jpg 1400w"
src="https://percona.community/blog/2023/05/04-bicicle.jpg" alt="Amsterdam Bicycle" />&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 src="https://percona.community/blog/2023/05/05-docker-community.jpeg" alt="Docker Captains" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_7700f94bcf2d2c3.JPG 480w, https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_b09ae86575012c0d.JPG 768w, https://percona.community/blog/2023/05/06-cncf-ambassadors_hu_1c7a7e407b0aeb33.JPG 1400w"
src="https://percona.community/blog/2023/05/06-cncf-ambassadors.JPG" alt="CNCF ambassadors breakfast" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/07-kuberentes-operators_hu_71012bd382d2162e.JPG 480w, https://percona.community/blog/2023/05/07-kuberentes-operators_hu_416aefb6a1340926.JPG 768w, https://percona.community/blog/2023/05/07-kuberentes-operators_hu_cae55e1fb1827ff2.JPG 1400w"
src="https://percona.community/blog/2023/05/07-kuberentes-operators.JPG" alt="Kubernetes Operator Panel Discussion" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/08-ebpf_hu_f32e6846c31f46c8.jpg 480w, https://percona.community/blog/2023/05/08-ebpf_hu_7ba6da22255833a.jpg 768w, https://percona.community/blog/2023/05/08-ebpf_hu_fa07e96002c796a4.jpg 1400w"
src="https://percona.community/blog/2023/05/08-ebpf.jpg" alt="Kubecon eBPF" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/05/09-raffle_hu_6ebda76d1a389918.jpg 480w, https://percona.community/blog/2023/05/09-raffle_hu_e02825ea1325de47.jpg 768w, https://percona.community/blog/2023/05/09-raffle_hu_ac3f6b8018f60f59.jpg 1400w"
src="https://percona.community/blog/2023/05/09-raffle.jpg" alt="Percona Raffle" />&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 src="https://percona.community/blog/2023/04/database-application.jpg" alt="Database application" />&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 src="https://percona.community/blog/2023/04/01-mjii-folders.jpg" alt="Project folder structure" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/04/start-new-feature_hu_e388473634f50f2c.jpg 480w, https://percona.community/blog/2023/04/start-new-feature_hu_6ee4b99be8b8f5d.jpg 768w, https://percona.community/blog/2023/04/start-new-feature_hu_c53b7015cc9294ce.jpg 1400w"
src="https://percona.community/blog/2023/04/start-new-feature.jpg" alt="How a Database Monitoring Tool Can Help a Developer" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2023/04/pmm-image-1.jpg" alt="How a Database Monitoring Tool Can Help a Developer" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/04/pmm-image-2_hu_48e6a15291707376.jpg 480w, https://percona.community/blog/2023/04/pmm-image-2_hu_4a5f1024da7cfc48.jpg 768w, https://percona.community/blog/2023/04/pmm-image-2_hu_97352fcff0751432.jpg 1400w"
src="https://percona.community/blog/2023/04/pmm-image-2.jpg" alt="Adding new functionality to the application" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/04/pmm-image-3_hu_5c9ea71ea64a484f.jpg 480w, https://percona.community/blog/2023/04/pmm-image-3_hu_85695c9e2820b9b9.jpg 768w, https://percona.community/blog/2023/04/pmm-image-3_hu_636f194f3cab85.jpg 1400w"
src="https://percona.community/blog/2023/04/pmm-image-3.jpg" alt="Something has gone differently than planned" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/04/pmm-image-4_hu_891228e9918cb0a6.jpg 480w, https://percona.community/blog/2023/04/pmm-image-4_hu_a0a21a0133c958f.jpg 768w, https://percona.community/blog/2023/04/pmm-image-4_hu_9b942b0fe9455c23.jpg 1400w"
src="https://percona.community/blog/2023/04/pmm-image-4.jpg" alt="Something has gone differently than planned" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/04/pmm-qan_hu_52cebfd6dc9a9489.jpg 480w, https://percona.community/blog/2023/04/pmm-qan_hu_87ebf89815b2406a.jpg 768w, https://percona.community/blog/2023/04/pmm-qan_hu_a5c9b2e9b0531c7b.jpg 1400w"
src="https://percona.community/blog/2023/04/pmm-qan.jpg" alt="Percona Monitoring and Management PMM - MongoDB - QAN" />&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 src="https://percona.community/blog/2023/04/pmm-image-5.jpg" alt="Percona Monitoring and Management PMM - Fixing the problem" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/04/pmm-image-6_hu_81842427908c9ee6.jpg 480w, https://percona.community/blog/2023/04/pmm-image-6_hu_3865e12470808635.jpg 768w, https://percona.community/blog/2023/04/pmm-image-6_hu_3fae836c84c0dfa9.jpg 1400w"
src="https://percona.community/blog/2023/04/pmm-image-6.jpg" alt="Percona Monitoring and Management PMM - Fixing the problem" />&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 src="https://percona.community/blog/2023/04/00-chat-gpt-generates-test-data.jpg" alt="ChaGPT respects referential integrity and prudently starts with reference tables" />&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 src="https://percona.community/blog/2023/04/01-robohash.png" alt="robohash" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_d9c69ee5e12a81e0.png 480w, https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_4527b2dedca11773.png 768w, https://percona.community/blog/2023/04/02-pagila-part-of-schema_hu_9f1368b1f6c498f5.png 1400w"
src="https://percona.community/blog/2023/04/02-pagila-part-of-schema.png" alt="The main part of the Pagila schema for our exercises" />&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 src="https://percona.community/blog/2023/03/php-3-structure-1.jpg" alt="PHP Structure - App Init" />&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 src="https://percona.community/blog/2023/03/php-3-structure-2.jpg" alt="PHP Structure - Functions" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/php-3-structure-3_hu_f611ddfbe48a3f5d.jpg 480w, https://percona.community/blog/2023/03/php-3-structure-3_hu_c6a1b1fb05422c94.jpg 768w, https://percona.community/blog/2023/03/php-3-structure-3_hu_743e9c6fe8682894.jpg 1400w"
src="https://percona.community/blog/2023/03/php-3-structure-3.jpg" alt="PHP Structure - Result" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-API-1_hu_d3c6e1ec07d1c518.jpg 480w, https://percona.community/blog/2023/03/PHP-API-1_hu_98064086919ed48b.jpg 768w, https://percona.community/blog/2023/03/PHP-API-1_hu_b7ae6512936e88d.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-API-1.jpg" alt="the GitHub REST API" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-API-2_hu_ad436c504b05a5a0.jpg 480w, https://percona.community/blog/2023/03/PHP-API-2_hu_c6a6c72347aeb769.jpg 768w, https://percona.community/blog/2023/03/PHP-API-2_hu_cb219cc1362cdeea.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-API-2.jpg" alt="the GitHub REST API - first request to the API" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-API-3_hu_af828c6b889b67bf.jpg 480w, https://percona.community/blog/2023/03/PHP-API-3_hu_b34144f26b53303a.jpg 768w, https://percona.community/blog/2023/03/PHP-API-3_hu_403d7fc530e6de7b.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-API-3.jpg" alt="the GitHub REST API - save the data to the database" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-API-4_hu_d1945bf2ee4bc54c.jpg 480w, https://percona.community/blog/2023/03/PHP-API-4_hu_bbff6a8f4ff4be8c.jpg 768w, https://percona.community/blog/2023/03/PHP-API-4_hu_59327d4a98e7e573.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-API-4.jpg" alt="the GitHub REST API - MongoDB Compass" />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-API-5_hu_bbb2e43b74ad3de2.jpg 480w, https://percona.community/blog/2023/03/PHP-API-5_hu_5df00295631adaec.jpg 768w, https://percona.community/blog/2023/03/PHP-API-5_hu_8646e7dda68aa54d.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-API-5.jpg" alt="the GitHub REST API - MongoDB Compass" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_d01c326e6e73214c.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_2c67a09d12a8250c.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-6_hu_6b9669afb6ffd19d.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-6.jpg" alt="Dockerfile for PHM + MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_21a6ba8c75e19294.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_b7f76e2ab41bc361.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-4_hu_9eace24a939ba5b1.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-4.jpg" alt="Run docker-compose" />&lt;/figure>&lt;/p>
&lt;p>Open localhost in the browser.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2023/03/PHP-Mongod-1-8.jpg" alt="PHP Info - Localhost - browser" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_4515c4abb81baea5.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_453f3c372a38e7e5.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-7_hu_c77eb195e792d90a.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-7.jpg" alt="Stop docker-compose" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_d6469f322ba2f6cf.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_6274b3fd7bbf365a.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-2_hu_757bfb16a98a1539.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-2.jpg" alt="Install required PHP packages to work with MongoDB" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_3a3d2b3359736bef.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_181f6e88846c9748.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-5_hu_a84d5f8775ebdff5.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-5.jpg" alt="Connecting to MongoDB via MongoDB Compass" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_c82f68933043a73d.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_b1ff412b6bfc46ce.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-3_hu_3df3a35cefd96cba.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-3.jpg" alt="Connecting to MongoDB via MongoDB Compass" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_56c5b94928054a4b.jpg 480w, https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_551af93396fbc222.jpg 768w, https://percona.community/blog/2023/03/PHP-Mongod-1-1_hu_adffafc44c347f5d.jpg 1400w"
src="https://percona.community/blog/2023/03/PHP-Mongod-1-1.jpg" alt="Connecting to MongoDB via MongoDB Compass" />&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 src="https://percona.community/blog/2023/03/backup.jpg" alt="Backup" />&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 src="https://percona.community/blog/2023/03/movienet_model.png" alt="Movienet Database" />&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 src="https://percona.community/blog/2023/03/01-interface.jpg" alt="Interface" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/02-long-query-response_hu_62a852a5133eca47.jpg 480w, https://percona.community/blog/2023/03/02-long-query-response_hu_c591c54cbe30a2db.jpg 768w, https://percona.community/blog/2023/03/02-long-query-response_hu_3267cec7f34660ea.jpg 1400w"
src="https://percona.community/blog/2023/03/02-long-query-response.jpg" alt="Long query Response" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_864290ce529632af.jpg 480w, https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_db70c270db2c3c4b.jpg 768w, https://percona.community/blog/2023/03/03-high-cpu-utilization_hu_a3a8d6ea22fbef2e.jpg 1400w"
src="https://percona.community/blog/2023/03/03-high-cpu-utilization.jpg" alt="High Cpu Utilization" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/04-high-memory-usage_hu_26bf5764a8cc862e.jpg 480w, https://percona.community/blog/2023/03/04-high-memory-usage_hu_44c022f77a424a53.jpg 768w, https://percona.community/blog/2023/03/04-high-memory-usage_hu_906f31d272252b92.jpg 1400w"
src="https://percona.community/blog/2023/03/04-high-memory-usage.jpg" alt="High Memory Usage" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/05-disk-io_hu_4a27991163e403f1.jpg 480w, https://percona.community/blog/2023/03/05-disk-io_hu_7be04ca93c7a3e22.jpg 768w, https://percona.community/blog/2023/03/05-disk-io_hu_6ed179d2eea2cc2a.jpg 1400w"
src="https://percona.community/blog/2023/03/05-disk-io.jpg" alt="Disk Io" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/06-user-conexion_hu_fbde4c457f6e6d56.jpg 480w, https://percona.community/blog/2023/03/06-user-conexion_hu_7077b7be7ebefd15.jpg 768w, https://percona.community/blog/2023/03/06-user-conexion_hu_2f45dabbef25cf92.jpg 1400w"
src="https://percona.community/blog/2023/03/06-user-conexion.jpg" alt="User Conexion" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/03/07-data-grown_hu_5c5fc49e74281736.jpg 480w, https://percona.community/blog/2023/03/07-data-grown_hu_6e0ef3cc7be596ed.jpg 768w, https://percona.community/blog/2023/03/07-data-grown_hu_e8ddf2f3e2ca5739.jpg 1400w"
src="https://percona.community/blog/2023/03/07-data-grown.jpg" alt="Data Grown" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/1-volume_hu_7c2971b9d8f8b858.png 480w, https://percona.community/blog/2023/02/1-volume_hu_c18f7cb63cd443b3.png 768w, https://percona.community/blog/2023/02/1-volume_hu_f342c14e99bb50bd.png 1400w"
src="https://percona.community/blog/2023/02/1-volume.png" alt="1-no-volume" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/2-ls_hu_de414575bf8202f4.png 480w, https://percona.community/blog/2023/02/2-ls_hu_75b0710387e86d0.png 768w, https://percona.community/blog/2023/02/2-ls_hu_bf400be65ca46ef9.png 1400w"
src="https://percona.community/blog/2023/02/2-ls.png" alt="2-ls" />&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 src="https://percona.community/blog/2023/02/3-image-no-volume.png" alt="3-image-no-volume" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/4-image-multiple-sql_hu_5b5689c245419b36.png 480w, https://percona.community/blog/2023/02/4-image-multiple-sql_hu_1c2329af4b746da4.png 768w, https://percona.community/blog/2023/02/4-image-multiple-sql_hu_254369876d9745e7.png 1400w"
src="https://percona.community/blog/2023/02/4-image-multiple-sql.png" alt="4-image-multiple-sql.png" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/5-no-volume_hu_ad7a1b2379229cf9.png 480w, https://percona.community/blog/2023/02/5-no-volume_hu_101c56ebf8822b3a.png 768w, https://percona.community/blog/2023/02/5-no-volume_hu_bb2f91ebe61513c1.png 1400w"
src="https://percona.community/blog/2023/02/5-no-volume.png" alt="5-image-volume" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/6-image-volume_hu_3cdcfe2f40298fae.png 480w, https://percona.community/blog/2023/02/6-image-volume_hu_931ff76363437249.png 768w, https://percona.community/blog/2023/02/6-image-volume_hu_5505923f1bc291ac.png 1400w"
src="https://percona.community/blog/2023/02/6-image-volume.png" alt="6-image-volume" />&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 src="https://percona.community/blog/2023/02/non_code.png" alt="Non Code" />&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 src="https://percona.community/blog/2023/02/non_code1.png" alt="Non Code" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/non_code2_hu_c371cbe0779e0f2f.png 480w, https://percona.community/blog/2023/02/non_code2_hu_4b930298ee971a75.png 768w, https://percona.community/blog/2023/02/non_code2_hu_4853f5d6343f35a.png 1400w"
src="https://percona.community/blog/2023/02/non_code2.png" alt="Non Code" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/non_code3_hu_9e05c3318b53610f.png 480w, https://percona.community/blog/2023/02/non_code3_hu_1fd9552f1e6fc0a1.png 768w, https://percona.community/blog/2023/02/non_code3_hu_d9068a12d99f9206.png 1400w"
src="https://percona.community/blog/2023/02/non_code3.png" alt="Non Code" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/02/petclinic_hu_141342c1c5d61197.png 480w, https://percona.community/blog/2023/02/petclinic_hu_bba157abaa156f1e.png 768w, https://percona.community/blog/2023/02/petclinic_hu_e045fca286d3aa54.png 1400w"
src="https://percona.community/blog/2023/02/petclinic.png" alt="Pet Clinic" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/01/pmm-login-screen_hu_56ea1277e5f7c4b7.png 480w, https://percona.community/blog/2023/01/pmm-login-screen_hu_75ec84a085ea39ca.png 768w, https://percona.community/blog/2023/01/pmm-login-screen_hu_b889a7e009f52ded.png 1400w"
src="https://percona.community/blog/2023/01/pmm-login-screen.png" alt="PMM Login Screen" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/01/pmm-home-dashboard_hu_39234b2ff6de287c.png 480w, https://percona.community/blog/2023/01/pmm-home-dashboard_hu_8f62f17f42acb0a9.png 768w, https://percona.community/blog/2023/01/pmm-home-dashboard_hu_7e88b1c2319e1589.png 1400w"
src="https://percona.community/blog/2023/01/pmm-home-dashboard.png" alt="PMM Home Dashboard" />&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 src="https://percona.community/blog/2023/01/multiprocessing.png" alt="Multiprocessing" />&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 sizes="100vw" srcset="https://percona.community/blog/2023/01/cpu-utilization_hu_f0cc9253bb228438.png 480w, https://percona.community/blog/2023/01/cpu-utilization_hu_ee7bfd78f9fb559f.png 768w, https://percona.community/blog/2023/01/cpu-utilization_hu_f74620294842ef4f.png 1400w"
src="https://percona.community/blog/2023/01/cpu-utilization.png" alt="CPU Utilization" />&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 src="https://percona.community/blog/2023/01/automate_xtrabackup_service_timer_example.jpg" alt="ServiceTimerExample" />&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 src="https://percona.community/blog/2023/01/automate_xtrabackup_archive_pic.png" alt="Archives" />&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 src="https://percona.community/blog/2022/12/dashboard1.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard2_hu_4f883bee6519618f.png 480w, https://percona.community/blog/2022/12/dashboard2_hu_19ef1c11b891a52c.png 768w, https://percona.community/blog/2022/12/dashboard2_hu_f3f9f955d990dc70.png 1400w"
src="https://percona.community/blog/2022/12/dashboard2.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard3_hu_a900b31c4ce4c67e.png 480w, https://percona.community/blog/2022/12/dashboard3_hu_6612840b4ceaa86.png 768w, https://percona.community/blog/2022/12/dashboard3_hu_a8ffacad0e4011dd.png 1400w"
src="https://percona.community/blog/2022/12/dashboard3.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard4_hu_3fc7568d367ed88.png 480w, https://percona.community/blog/2022/12/dashboard4_hu_d6e1de04a25b6a4d.png 768w, https://percona.community/blog/2022/12/dashboard4_hu_3119cf56c7f1ae67.png 1400w"
src="https://percona.community/blog/2022/12/dashboard4.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard5_hu_705a86506d5d6849.png 480w, https://percona.community/blog/2022/12/dashboard5_hu_1609f30195d2bae4.png 768w, https://percona.community/blog/2022/12/dashboard5_hu_35a05bfdb57e519f.png 1400w"
src="https://percona.community/blog/2022/12/dashboard5.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard6_hu_2271ceb1aaf41250.png 480w, https://percona.community/blog/2022/12/dashboard6_hu_5f34c6ce1572be43.png 768w, https://percona.community/blog/2022/12/dashboard6_hu_d481a0262129c3fa.png 1400w"
src="https://percona.community/blog/2022/12/dashboard6.png" alt="Dashboard" />&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 src="https://percona.community/blog/2022/12/dashboard7.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard8_hu_fcac0fff3e0a699b.png 480w, https://percona.community/blog/2022/12/dashboard8_hu_862e531127fe1679.png 768w, https://percona.community/blog/2022/12/dashboard8_hu_67b47babd5dd53d2.png 1400w"
src="https://percona.community/blog/2022/12/dashboard8.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard9_hu_978df235540313a.png 480w, https://percona.community/blog/2022/12/dashboard9_hu_a82ee5ee11ee470e.png 768w, https://percona.community/blog/2022/12/dashboard9_hu_856ff2af0d86f3e8.png 1400w"
src="https://percona.community/blog/2022/12/dashboard9.png" alt="Dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/12/dashboard10_hu_eadfa7b7c6863d44.png 480w, https://percona.community/blog/2022/12/dashboard10_hu_5a45558c07e8cafc.png 768w, https://percona.community/blog/2022/12/dashboard10_hu_a8843f0dd9ef501d.png 1400w"
src="https://percona.community/blog/2022/12/dashboard10.png" alt="Dashboard" />&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 src="https://percona.community/blog/2022/12/pagila-schema-output.png" alt="Output" />&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 src="https://percona.community/blog/2022/12/pagila-data-output.png" alt="Output" />&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 src="https://percona.community/blog/2022/12/dt-output.png" alt="Output" />&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 src="https://percona.community/blog/2022/12/inventory-output.png" alt="Output" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_8c52896cbd760956.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_852762b2aa8ce665.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city_hu_4e512c4613df7dee.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city.jpg" alt="AWS Summit Mexico City" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_19bee353c9c4faa6.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_d0515664cbaa81db.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-expo_hu_c67c5cf344a8b6d2.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-expo.jpg" alt="AWS Summit Mexico City Expo" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_ce5871579cd1399.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_a65f59f0f02c112b.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller_hu_263e3ac42f9814a1.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-kevin-miller.jpg" alt="AWS Summit Mexico City - Kevin Miller" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_edc203073e06ba81.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_850460fa23421095.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure_hu_451d7050a5f6d0e7.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-global-infrastructure.jpg" alt="AWS Summit Mexico City - Global Infrastructure" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_718e5fc1f9708292.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_dd6251991e61467a.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-nu_hu_d0d0c5d50aeaf1a7.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-nu.jpg" alt="AWS Summit Mexico City - Nu" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_e5c50c9cca1657bf.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_80838235092f2ad1.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi_hu_64737893ab9209c8.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-contpaqi.jpg" alt="AWS Summit Mexico City - CONTPAQi" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_16d4737001162a59.jpg 480w, https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_9b31ba2138ff100c.jpg 768w, https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians_hu_f950f99e605106e5.jpg 1400w"
src="https://percona.community/blog/2022/10/aws-summit-mexico-city-perconians.jpg" alt="AWS Summit Mexico City - Perconians" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/perconians-queretaro_hu_b75394a390abd93c.jpg 480w, https://percona.community/blog/2022/10/perconians-queretaro_hu_a102b847985c37cc.jpg 768w, https://percona.community/blog/2022/10/perconians-queretaro_hu_1ecf907bd03cf25b.jpg 1400w"
src="https://percona.community/blog/2022/10/perconians-queretaro.jpg" alt="Perconians at Querétaro" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/with-operators_hu_25016fe3b8ee1355.png 480w, https://percona.community/blog/2022/13/with-operators_hu_e7bb015fe7e101a6.png 768w, https://percona.community/blog/2022/13/with-operators_hu_858a6341bc137533.png 1400w"
src="https://percona.community/blog/2022/13/with-operators.png" alt="With Operators" />&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Without Operators&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2022/13/without-operators_hu_5377d9c9d2b94577.png 480w, https://percona.community/blog/2022/13/without-operators_hu_f7bbc57613f173b6.png 768w, https://percona.community/blog/2022/13/without-operators_hu_3c994909e3b25a95.png 1400w"
src="https://percona.community/blog/2022/13/without-operators.png" alt="Without Operators" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/1-operators-gcloud_hu_de723b372159705f.png 480w, https://percona.community/blog/2022/13/1-operators-gcloud_hu_ea395a17e92310cb.png 768w, https://percona.community/blog/2022/13/1-operators-gcloud_hu_861f73d47bb21ff9.png 1400w"
src="https://percona.community/blog/2022/13/1-operators-gcloud.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/2-operators-get-credentials_hu_8097b375aa73fbcf.png 480w, https://percona.community/blog/2022/13/2-operators-get-credentials_hu_e5b63776963c800b.png 768w, https://percona.community/blog/2022/13/2-operators-get-credentials_hu_e169e84be299341e.png 1400w"
src="https://percona.community/blog/2022/13/2-operators-get-credentials.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_f39b8053e9ddaeca.png 480w, https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_f39683c329e3ef82.png 768w, https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding_hu_eee4cd83e1a80d7.png 1400w"
src="https://percona.community/blog/2022/13/3-kubectl-create-cluisterrolebinding.png" alt="Overview" />&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 src="https://percona.community/blog/2022/13/4-kubectl-create-namespace.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_1080c21e94cf84db.png 480w, https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_4ea4341c3e043911.png 768w, https://percona.community/blog/2022/13/5-kubectl-config-set-contex_hu_deef002f9d32086f.png 1400w"
src="https://percona.community/blog/2022/13/5-kubectl-config-set-contex.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_b7cfd39a938c998.png 480w, https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_51373f5156f5b8fb.png 768w, https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle_hu_80edbe4bcf31daa8.png 1400w"
src="https://percona.community/blog/2022/13/6-kubectl-apply-f-bundle.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_e15f0b4f5f83c92b.png 480w, https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_beee7a6c3f1bcc2f.png 768w, https://percona.community/blog/2022/13/7-kubectl-apply-f-cr_hu_b93531c29e27188.png 1400w"
src="https://percona.community/blog/2022/13/7-kubectl-apply-f-cr.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_ab0b66e14d3e0dce.png 480w, https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_3b90fcee2f23f22e.png 768w, https://percona.community/blog/2022/13/8-kubectl-get-psmdb_hu_d1b125d7248e7401.png 1400w"
src="https://percona.community/blog/2022/13/8-kubectl-get-psmdb.png" alt="Overview" />&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 src="https://percona.community/blog/2022/13/9-kubectl-get-secret.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/10-decode_hu_da6ebad61ea4ad21.png 480w, https://percona.community/blog/2022/13/10-decode_hu_29ea244e25562ac0.png 768w, https://percona.community/blog/2022/13/10-decode_hu_769410ebe71992c5.png 1400w"
src="https://percona.community/blog/2022/13/10-decode.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/11-get-services_hu_e92af432fa97bcfe.png 480w, https://percona.community/blog/2022/13/11-get-services_hu_42b31a5d2454eb28.png 768w, https://percona.community/blog/2022/13/11-get-services_hu_6c04eb35762d6405.png 1400w"
src="https://percona.community/blog/2022/13/11-get-services.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/12-run-docker-container_hu_356e62773d644d48.png 480w, https://percona.community/blog/2022/13/12-run-docker-container_hu_bff79e73a0bb2abe.png 768w, https://percona.community/blog/2022/13/12-run-docker-container_hu_4a6102786c9b3333.png 1400w"
src="https://percona.community/blog/2022/13/12-run-docker-container.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/13/13-mongo_hu_63f0d96c6ef06538.png 480w, https://percona.community/blog/2022/13/13-mongo_hu_7d8ef4e37f86ba6e.png 768w, https://percona.community/blog/2022/13/13-mongo_hu_758cba1cf4d7227c.png 1400w"
src="https://percona.community/blog/2022/13/13-mongo.png" alt="Overview" />&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 src="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-intro.png" alt="Overview" />&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 src="https://percona.community/blog/2022/10/recap-mpdm-hacktoberfest-youtube.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_a449d5fff5c26b51.png 480w, https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_a4e8725f8f3da19.png 768w, https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories_hu_9cce59d5833879ab.png 1400w"
src="https://percona.community/blog/2022/10/recap-mdpdm-hacktoberfest-repositories.png" alt="Overview" />&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 src="https://percona.community/blog/2022/10/recap-percona-docker.png" alt="Overview" />&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 src="https://percona.community/blog/2022/10/recap-pmm.png" alt="Overview" />&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 src="https://percona.community/blog/2022/10/recap-operator.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/9/eksctl_running_hu_c15eff3eca8ba295.png 480w, https://percona.community/blog/2022/9/eksctl_running_hu_8bb0aeea46f4047d.png 768w, https://percona.community/blog/2022/9/eksctl_running_hu_bf12dad224322e69.png 1400w"
src="https://percona.community/blog/2022/9/eksctl_running.png" alt="eksctl running" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/9/eks_cluster_hu_ff679d01ae9bc6d3.png 480w, https://percona.community/blog/2022/9/eks_cluster_hu_4ab0e18f253db1d7.png 768w, https://percona.community/blog/2022/9/eks_cluster_hu_b29bc1f177b5650d.png 1400w"
src="https://percona.community/blog/2022/9/eks_cluster.png" alt="EKS Cluster" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_130c66d5b3f9665a.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_7a58c512dd9cc0f9.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-overview_hu_82edbc1ac276728a.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-overview.png" alt="Overview" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_778334322893c109.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_ea22912ba5ddf4b4.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps_hu_73f8a686032ac833.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-docker-ps.png" alt="docker ps" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_f3e2a7e99a4fad50.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_5a53ccebcba11bba.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard_hu_db72bae5247480bf.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-dashboard.png" alt="pmm-ubuntu-pmm-dashboard" />&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 src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-v.png" alt="pmm-ubuntu-pmm-dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_eaaf118b3b5e1001.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_6e04aa1898999067.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-show-grants_hu_e2b21b7188491d1b.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-show-grants.png" alt="pmm-ubuntu-show-grants" />&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 src="https://percona.community/blog/2022/8/pmm-ubuntu-hostname-i.png" alt="pmm-ubuntu-hostname-i" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_2c92046682bc7a77.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_671dd038024060eb.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config_hu_a94187a076dfb2be.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-config.png" alt="pmm-ubuntu-pmm-admin-config" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_58d197706bc000c5.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_beb71ec4c1d61705.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory_hu_5a81dc5f4682635d.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-inventory.png" alt="pmm-ubuntu-hostname-i" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_aa3fe358f469ad93.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_4888b439ca1fd46a.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql_hu_762478fafd9b7c38.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-pmm-admin-add-sql.png" alt="pmm-ubuntu-pmm-admin-add-sql" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_7f077b03e0f2bfa4.png 480w, https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_bddc5b9da8193857.png 768w, https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard_hu_20a42dc5d29c7d3b.png 1400w"
src="https://percona.community/blog/2022/8/pmm-ubuntu-last-dashboard.png" alt="pmm-ubuntu-last-dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/8/pmm-dashboard_hu_eeaae8257f6da4fe.png 480w, https://percona.community/blog/2022/8/pmm-dashboard_hu_9dc49d98a8d66708.png 768w, https://percona.community/blog/2022/8/pmm-dashboard_hu_ec34be7f15cd5e5e.png 1400w"
src="https://percona.community/blog/2022/8/pmm-dashboard.png" alt="PMM Dashboard" />&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 src="https://percona.community/blog/2022/7/rivers.png" alt="Rivers" />&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 src="https://percona.community/blog/2022/7/axis.png" alt="Axis" />&lt;/figure>&lt;/p>
&lt;p>Let’s look at a more complicated query with subqueries:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2022/7/axis-2.png" alt="Axis" />&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 src="https://percona.community/blog/2022/6/sql_everywhere.jpg" alt="What is dbt" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/6/what_is_dbt_hu_731ebbd3f1801419.jpg 480w, https://percona.community/blog/2022/6/what_is_dbt_hu_e9a784177960ca75.jpg 768w, https://percona.community/blog/2022/6/what_is_dbt_hu_2d7e00be3b5533bc.jpg 1400w"
src="https://percona.community/blog/2022/6/what_is_dbt.jpg" alt="What is dbt" />&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 src="https://percona.community/blog/2022/5/TheMySQLWorkshopBook.jpg" alt="The MySQL Workshop Book Review" />&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 src="https://percona.community/blog/2022/4/percona-systemctl-status.png" alt="Percona Status" />&lt;/figure>&lt;/li>
&lt;li>Command Line Interface:
&lt;figure>&lt;img src="https://percona.community/blog/2022/4/percona-server-running.png" alt="CLI Example" />&lt;/figure>&lt;/li>
&lt;li>XtraBackup complete:
&lt;figure>&lt;img src="https://percona.community/blog/2022/4/percona-xtrabackup.png" alt="Complete Backup" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_2dc374f7ca353e7.png 480w, https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_f01448cf0873e467.png 768w, https://percona.community/blog/2022/2/1-Forking-Percona-Community_hu_3e2f702ef3a5fa47.png 1400w"
src="https://percona.community/blog/2022/2/1-Forking-Percona-Community.png" alt="Fork" />&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 src="https://percona.community/blog/2022/2/2-Git-Clone-Button.png" alt="Clone" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/5-Browser-Test_hu_17e0d69c9fde9ad5.png 480w, https://percona.community/blog/2022/2/5-Browser-Test_hu_93c5409f70248cb1.png 768w, https://percona.community/blog/2022/2/5-Browser-Test_hu_fc32454e13797870.png 1400w"
src="https://percona.community/blog/2022/2/5-Browser-Test.png" alt="Browser" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/5-Browser-Test_hu_17e0d69c9fde9ad5.png 480w, https://percona.community/blog/2022/2/5-Browser-Test_hu_93c5409f70248cb1.png 768w, https://percona.community/blog/2022/2/5-Browser-Test_hu_fc32454e13797870.png 1400w"
src="https://percona.community/blog/2022/2/5-Browser-Test.png" alt="Browser Test" />&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 src="https://percona.community/blog/2022/2/6-git-status.png" alt="Git Status" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/7-git-commit_hu_a112bf7eecbe1e1d.png 480w, https://percona.community/blog/2022/2/7-git-commit_hu_1ad40edb31a06765.png 768w, https://percona.community/blog/2022/2/7-git-commit_hu_96a0081ab4792746.png 1400w"
src="https://percona.community/blog/2022/2/7-git-commit.png" alt="Git Commit" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/8-GitHub-Branch_hu_f9b7ead7a8f15e01.png 480w, https://percona.community/blog/2022/2/8-GitHub-Branch_hu_78379f262469f017.png 768w, https://percona.community/blog/2022/2/8-GitHub-Branch_hu_86fc6010a1738def.png 1400w"
src="https://percona.community/blog/2022/2/8-GitHub-Branch.png" alt="GitHub Branch" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_be3d93b4e96aaf87.png 480w, https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_70fe9de53349957b.png 768w, https://percona.community/blog/2022/2/9-GitHub-Pull-Request_hu_fd65869e7ea90082.png 1400w"
src="https://percona.community/blog/2022/2/9-GitHub-Pull-Request.png" alt="GitHub Branch" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_bcb7233729ad28a9.png 480w, https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_a309b587b64eb9fa.png 768w, https://percona.community/blog/2022/2/10-GitHub-Check-PR_hu_9b5da2ed631c8f13.png 1400w"
src="https://percona.community/blog/2022/2/10-GitHub-Check-PR.png" alt="GitHub Check PR" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/1/move_hu_a7820a12e73872ca.png 480w, https://percona.community/blog/2022/1/move_hu_396b32e948dbc00e.png 768w, https://percona.community/blog/2022/1/move_hu_bba934b29a032821.png 1400w"
src="https://percona.community/blog/2022/1/move.png" alt="Spikes" />&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 sizes="100vw" srcset="https://percona.community/blog/2022/1/innodb_hu_55d3fb89c55056ca.png 480w, https://percona.community/blog/2022/1/innodb_hu_bcff448b89085994.png 768w, https://percona.community/blog/2022/1/innodb_hu_763774e0526e18b1.png 1400w"
src="https://percona.community/blog/2022/1/innodb.png" alt="InnoDB" />&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 src="https://percona.community/../assets/blog/2021/10/directory.png" alt="directory" />&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 src="https://percona.community/blog/2021/09/lostart-01.png" alt="lostart-10" />&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 src="https://percona.community/blog/2021/09/lostart-02.png" alt="lostart-10" />&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 sizes="100vw" srcset="https://percona.community/blog/2021/09/lostart-03_hu_5141778ed22a6fd5.png 480w, https://percona.community/blog/2021/09/lostart-03_hu_1c375d4cb25f1391.png 768w, https://percona.community/blog/2021/09/lostart-03_hu_649f1469cd09bbe4.png 1400w"
src="https://percona.community/blog/2021/09/lostart-03.png" alt="lostart-10" />&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 src="https://percona.community/blog/2021/09/lostart-04.png" alt="lostart-10" />&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 sizes="100vw" srcset="https://percona.community/blog/2021/08/human-2_hu_28ef8337b564d094.jpg 480w, https://percona.community/blog/2021/08/human-2_hu_b49864fbe73a7c9d.jpg 768w, https://percona.community/blog/2021/08/human-2_hu_8ff4713ccdd1669b.jpg 1400w"
src="https://percona.community/blog/2021/08/human-2.jpg" alt="Scaling the Solution" />&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 src="https://percona.community/blog/2021/07/lbis-2.png" alt="lbis-2" />&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 src="https://percona.community/blog/2021/07/lbis-3.png" alt="lbis-3" />&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 src="https://percona.community/blog/2021/07/lbis-4.png" alt="lbis-4" />&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 src="https://percona.community/blog/2021/07/lbis-6.png" alt="lbis-6" />&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 src="https://percona.community/blog/2021/07/lbis-7.png" alt="lbis-7" />&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 src="https://percona.community/blog/2021/07/lbis-9.png" alt="lbis-9" />&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 src="https://percona.community/blog/2021/07/lbis-15.png" alt="lbis-15" />&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 src="https://percona.community/blog/2021/07/lbis-16.png" alt="lbis-16" />&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 src="https://percona.community/blog/2021/07/lbis-10.png" alt="lbis-10" />&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 src="https://percona.community/blog/2021/07/lbis-11.png" alt="lbis-11" />&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 src="https://percona.community/blog/2021/07/lbis-13.png" alt="lbis-13" />&lt;/figure>&lt;/p>
&lt;center> &lt;b>Slave2&lt;/b> &lt;/center>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2021/07/lbis-12.png" alt="lbis-12" />&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 src="https://percona.community/blog/2021/07/lbis-14.png" alt="lbis-14" />&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 src="https://percona.community/blog/2021/07/prometheus-exporter.png" alt="prometheus exporter" />&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 src="https://percona.community/blog/2021/07/query-exporter.png" alt="query exporter" />&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 sizes="100vw" srcset="https://percona.community/blog/2021/07/metric-results_hu_f42480f2db3de72e.png 480w, https://percona.community/blog/2021/07/metric-results_hu_ea7e641939b8be9b.png 768w, https://percona.community/blog/2021/07/metric-results_hu_c95cb9f17d9c0c0f.png 1400w"
src="https://percona.community/blog/2021/07/metric-results.png" alt="metric results" />&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 src="https://percona.community/blog/2020/10/image1.jpg" alt="tarantool start" />&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 src="https://percona.community/blog/2020/10/image3-1.png" alt="tarantool 1" />&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 src="https://percona.community/blog/2020/10/image2-1.png" alt="tarantool 2" />&lt;/figure> Procession of delayed tasks can be added:
&lt;figure>&lt;img src="https://percona.community/blog/2020/10/image5.png" alt="tarantool 3" />&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 src="https://percona.community/blog/2020/10/image4-1.png" alt="tarantool 4" />&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 src="https://percona.community/blog/2020/10/image1-1.png" alt="Zero downtime schema change with Liquibase &amp; Percona" />&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 src="https://percona.community/blog/2020/10/image1.png" alt="Mayastor 1" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/10/image3_hu_87e0210307088601.png 480w, https://percona.community/blog/2020/10/image3_hu_8de5514575f4b675.png 768w, https://percona.community/blog/2020/10/image3_hu_4fca178fb10e261.png 1400w"
src="https://percona.community/blog/2020/10/image3.png" alt="A screenshot showing Mayastor in action" />&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 src="https://percona.community/blog/2020/09/image3.png" alt="How to Write Your Index in Tarantool" />&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 src="https://percona.community/blog/2020/09/image5.png" alt="How to Write Your Index in Tarantool 2" />&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 src="https://percona.community/blog/2020/09/image4.png" alt="How to Write Your Index in Tarantool 3" />&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 src="https://percona.community/blog/2020/09/1.png" alt="How to Write Your Index in Tarantool Cover 15" />&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 src="https://percona.community/blog/2020/09/2.png" alt="How to Write Your Index in Tarantool Cover 14" />&lt;/figure>
[Required memory in megabytes / Dimensions]&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2020/09/3.png" alt="How to Write Your Index in Tarantool Cover 12" />&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 src="https://percona.community/blog/2020/09/4.png" alt="How to Write Your Index in Tarantool Cover 11" />&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 src="https://percona.community/blog/2020/09/mariadb_fest_video.jpeg" alt="MariaDB Fest 2020" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/08/iot1-scaled_hu_268480ab1f6ed250.jpg 480w, https://percona.community/blog/2020/08/iot1-scaled_hu_8c06d89066a05198.jpg 768w, https://percona.community/blog/2020/08/iot1-scaled_hu_8bd5e8192c40d276.jpg 1400w"
src="https://percona.community/blog/2020/08/iot1-scaled.jpg" alt=" " />&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 src="https://percona.community/blog/2020/07/image6.png" alt="IIoT platform databases - How Mail.ru Cloud Solutions" />&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 src="https://percona.community/blog/2020/07/image1.png" alt="Mail.ru IoT Platform set-up" />&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 src="https://percona.community/blog/2020/07/image3.png" alt="Flowchart of the Tarantool-based prototype" />&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 src="https://percona.community/blog/2020/07/image9.png" alt="Kubernetes" />&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 src="https://percona.community/blog/2020/07/image11.png" alt="Checking our requirements with the CAP theorem" />&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 src="https://percona.community/blog/2020/07/image10.png" alt="ACID" />&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 src="https://percona.community/blog/2020/07/image4.png" alt="This is how the device tree looks like" />&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 src="https://percona.community/blog/2020/07/image7.png" alt="A possible, but not a perfect solution with two databases" />&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 src="https://percona.community/blog/2020/07/image5.png" alt="This is how we dealt with poor writing performance" />&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 src="https://percona.community/blog/2020/07/image2.png" alt="ClickHouse doesn’t handle a large number of queries well" />&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 src="https://percona.community/blog/2020/07/image8.png" alt="Final architecture: ClickHouse as the analytic database and the Tarantool cache storing 24 hours of data. " />&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 src="https://percona.community/blog/2020/06/image3-1.jpg" alt="A still from “Our Secret Universe: The Hidden Life of the Cell”" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/01_hu_6c76bae24ff8a265.png 480w, https://percona.community/blog/2020/06/01_hu_bfe1956e037059d0.png 768w, https://percona.community/blog/2020/06/01_hu_fa9ec83dbbf48cf8.png 1400w"
src="https://percona.community/blog/2020/06/01.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/02_hu_3dc2879c9a470c4b.png 480w, https://percona.community/blog/2020/06/02_hu_c5ebb60eeaf4797a.png 768w, https://percona.community/blog/2020/06/02_hu_3713023a6c6ddadd.png 1400w"
src="https://percona.community/blog/2020/06/02.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/03_hu_a3f56fff190cd256.png 480w, https://percona.community/blog/2020/06/03_hu_bda484893d9d2c6f.png 768w, https://percona.community/blog/2020/06/03_hu_b6dbd2707453349b.png 1400w"
src="https://percona.community/blog/2020/06/03.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/04_hu_79058a61cc2fe854.png 480w, https://percona.community/blog/2020/06/04_hu_e1e1de4a2dee9fea.png 768w, https://percona.community/blog/2020/06/04_hu_148619a4fc23bb63.png 1400w"
src="https://percona.community/blog/2020/06/04.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/05_hu_6b9e3d9084a7fae4.png 480w, https://percona.community/blog/2020/06/05_hu_8d28450581d69338.png 768w, https://percona.community/blog/2020/06/05_hu_b74eb4e74c0b768e.png 1400w"
src="https://percona.community/blog/2020/06/05.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/06_hu_95ea3e9b8b5a9242.png 480w, https://percona.community/blog/2020/06/06_hu_9432fd1269f70d3a.png 768w, https://percona.community/blog/2020/06/06_hu_a55b96c5d43a79a1.png 1400w"
src="https://percona.community/blog/2020/06/06.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>We implemented an authentication system.&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2020/06/07_hu_313da98a3d80d778.png 480w, https://percona.community/blog/2020/06/07_hu_c528fe72569b6d99.png 768w, https://percona.community/blog/2020/06/07_hu_e258359b99dd6049.png 1400w"
src="https://percona.community/blog/2020/06/07.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/08_hu_1eeb55f457120d84.png 480w, https://percona.community/blog/2020/06/08_hu_b80c01b14bb105bf.png 768w, https://percona.community/blog/2020/06/08_hu_acc71fa683a2d90c.png 1400w"
src="https://percona.community/blog/2020/06/08.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/09_hu_d0f0f78948103ab9.png 480w, https://percona.community/blog/2020/06/09_hu_8e968f91d3b10011.png 768w, https://percona.community/blog/2020/06/09_hu_dfbdeae9feb5c4f2.png 1400w"
src="https://percona.community/blog/2020/06/09.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/10_hu_9145d35b7b843dc9.png 480w, https://percona.community/blog/2020/06/10_hu_ef2f0a3983420e14.png 768w, https://percona.community/blog/2020/06/10_hu_34354b8de21f8612.png 1400w"
src="https://percona.community/blog/2020/06/10.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2020/06/11.png" alt=" " />&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 src="https://percona.community/blog/2020/06/12.png" alt=" " />&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 src="https://percona.community/blog/2020/06/13.png" alt=" " />&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 src="https://percona.community/blog/2020/06/14.png" alt=" " />&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 src="https://percona.community/blog/2020/06/15.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/16_hu_5f402beaf6507575.png 480w, https://percona.community/blog/2020/06/16_hu_315743cab1a85b6b.png 768w, https://percona.community/blog/2020/06/16_hu_3f75d8c5a2665cbb.png 1400w"
src="https://percona.community/blog/2020/06/16.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/17_hu_6f5fc35a6532a30c.png 480w, https://percona.community/blog/2020/06/17_hu_157e09bbc0d374b2.png 768w, https://percona.community/blog/2020/06/17_hu_720f3a7568f127b8.png 1400w"
src="https://percona.community/blog/2020/06/17.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/18_hu_9efdf2f925870807.png 480w, https://percona.community/blog/2020/06/18_hu_210e7565b9b4419c.png 768w, https://percona.community/blog/2020/06/18_hu_d788813a60e48095.png 1400w"
src="https://percona.community/blog/2020/06/18.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/19_hu_ea06d93aaad9b36.png 480w, https://percona.community/blog/2020/06/19_hu_f70d169b674fb513.png 768w, https://percona.community/blog/2020/06/19_hu_502a0814ee50b35b.png 1400w"
src="https://percona.community/blog/2020/06/19.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/20_hu_c2f96a07a49c0a5a.png 480w, https://percona.community/blog/2020/06/20_hu_85cab6dc00e7f9ff.png 768w, https://percona.community/blog/2020/06/20_hu_4c07596c4146a34c.png 1400w"
src="https://percona.community/blog/2020/06/20.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/21_hu_fc464180ffc8f1f7.png 480w, https://percona.community/blog/2020/06/21_hu_577315dcad6b6b5.png 768w, https://percona.community/blog/2020/06/21_hu_b2398fd3fcc40490.png 1400w"
src="https://percona.community/blog/2020/06/21.png" alt=" " />&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 src="https://percona.community/blog/2020/06/image6.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/image4.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/image3.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/image7.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;h2 id="the-player-moves">The player moves&lt;/h2>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2020/06/image2.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/image5.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/image1.jpg" alt=" " />&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 src="https://percona.community/blog/2020/06/google-summer-of-code-2019-367x263-1.jpg" alt="GSC" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_f4c4b9dc5bb35b6d.png 480w, https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_22c2a0a06ebe3748.png 768w, https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09_hu_5510cc0c2e035ce9.png 1400w"
src="https://percona.community/blog/2020/06/Screenshot-2020-06-04-at-15.25.09.png" alt=" " />&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 src="https://percona.community/blog/2020/06/Meet_Patel-1.jpg" alt="GSoC Student Meet Patel" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_1224c9e26680dbd7.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_a4961c23f2cda340.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_1_hu_2cbd147be7e3383a.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_1.png" alt="How to contribute Dashboards to PMM" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_ac51fe1a53140671.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_d0a0dd3a02f6dae5.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_2_hu_f5bdf90544a46f02.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_2.png" alt="Tooltips" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_d69d839c5295f200.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_217d4ad656de286d.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_3_hu_fa43f52ea226c9ea.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_3.png" alt="PMM Dashboard Settings" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_f6f930a9b5139277.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_18ac7eff3bc7d97f.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_4_hu_13820ee010699bd0.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_4.png" alt="PMM Dashboards Jira Issue" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_243442a762395303.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_5838e5871721a3aa.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_5_hu_cdd4cc556f79a476.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_5.png" alt="PHH Dashboards Contribution GitHub " />&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 src="https://percona.community/blog/2020/05/Contribute_to_dashboards_6.png" alt="Changing the Dashboard is easy" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_8f151d519b2757e7.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_1ca571d6b48e164d.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_7_hu_a643fa4e50afc16a.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_7.png" alt=" Description field" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_f2b335c81679041c.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_80b1b666ec5112bb.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_8_hu_65b8986e18361b25.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_8.png" alt="JSON" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_fef24fe560f0821a.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_52629108d3db8c68.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_9_hu_64fd6b4c20612356.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_9.png" alt="Contributor License Agreement" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Codecov_hu_e4f19d930417c19b.png 480w, https://percona.community/blog/2020/05/Codecov_hu_f54b89c83973ad05.png 768w, https://percona.community/blog/2020/05/Codecov_hu_df2850f90d7f5f63.png 1400w"
src="https://percona.community/blog/2020/05/Codecov.png" alt="Codecov" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Jenkins-1_hu_a80eb4198a908216.png 480w, https://percona.community/blog/2020/05/Jenkins-1_hu_3aa04e326a9f6f21.png 768w, https://percona.community/blog/2020/05/Jenkins-1_hu_3e77879a0f61ced8.png 1400w"
src="https://percona.community/blog/2020/05/Jenkins-1.png" alt="Jenkins" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_d02ee39bfbf43cd9.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_6c7c30e65f7fb9e2.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_14_hu_131b30fd6a3debbf.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_14.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_1a5aa20cc996caf5.png 480w, https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_1357c6d6546f41b9.png 768w, https://percona.community/blog/2020/05/Contribute_to_dashboards_15_hu_9bbdea43a5e32fda.png 1400w"
src="https://percona.community/blog/2020/05/Contribute_to_dashboards_15.png" alt="Result" />&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 src="https://percona.community/blog/2020/04/xeyzb9g_fodczvifb-xym4-qdwa.png" alt=" " />&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 src="https://percona.community/blog/2020/04/faster-alter-table.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/increasingly-dirty-buffer-pool-1.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/six-fold-increase.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/alter-table-different-on-larger-database.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/table-rebuild-increasingly-dirty.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/time-to-clear-pages.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/low-buffer-pool-io.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/high-buffer-pool-io.jpg" alt=" " />&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 src="https://percona.community/blog/2020/04/online-meetups-percona-linkedin.jpg" alt="Offer of Percona Speakers for events" />&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 src="https://percona.community/blog/2020/04/ple-1.jpg" alt="Percona Live Amsterdam 2019" />&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 src="https://percona.community/blog/2020/03/ycgmxxaqlvyslsrzoo6jnv1vmx0.jpeg" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_d60c256f811177d.png 480w, https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_39b506b33d58bc91.png 768w, https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw_hu_d739c0e5445c4fda.png 1400w"
src="https://percona.community/blog/2020/03/sw0zgm53me7ft63db8lxrswcxvw.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_f3530c8a57d7c29a.png 480w, https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_52e390899ba1b721.png 768w, https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4_hu_eaa10f291e8abfa1.png 1400w"
src="https://percona.community/blog/2020/03/g_vd787hpifhrlhk-jfs5sw8iu4.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_8840dfb2106fc197.png 480w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_74a2d65dc4e7561f.png 768w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw_hu_5818138e253e8692.png 1400w"
src="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_ac702cd470cdfca0.png 480w, https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_c64051aa7b1240ae.png 768w, https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm_hu_8b0d54851f517d89.png 1400w"
src="https://percona.community/blog/2020/03/iyb2ff_a6dryhg0nik8p48rhqhm.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_8840dfb2106fc197.png 480w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_74a2d65dc4e7561f.png 768w, https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1_hu_5818138e253e8692.png 1400w"
src="https://percona.community/blog/2020/03/geombgvhy6plfnrwpz0o8jqgnaw-1.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_b563d99993e0f93a.png 480w, https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_ae4b00115282bc1c.png 768w, https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc_hu_6dd579a829e99abf.png 1400w"
src="https://percona.community/blog/2020/03/r_hqbmgxwbkvcycacj7pnotjhjc.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_569c83fd96041b5d.png 480w, https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_53a944521b7a2b30.png 768w, https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia_hu_d0491e8d8d7668b6.png 1400w"
src="https://percona.community/blog/2020/03/hfwftcua4yyueyi0qgngr2mveia.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_ebd7b874bffe8ac9.png 480w, https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_726f3c0260eeb5d2.png 768w, https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk_hu_5527f90769fa6449.png 1400w"
src="https://percona.community/blog/2020/03/tlaoue_hclprc1i6tdjuil5efyk.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_7b5c0566f09462b8.png 480w, https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_fc382e84992dc5d9.png 768w, https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi_hu_9dec9a5e25973dee.png 1400w"
src="https://percona.community/blog/2020/03/7bpnxxuydmfmddneontw1nxexi.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_db8d6714a3873ada.png 480w, https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_7304cbe41e7e3909.png 768w, https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s_hu_a2223e181f00b74b.png 1400w"
src="https://percona.community/blog/2020/03/nkmofqnvvoccqfqkyz1myryu4-s.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_434b84d991bce5e5.png 480w, https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_c7e372080bdc6dda.png 768w, https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w_hu_da41c0d3ba615cc8.png 1400w"
src="https://percona.community/blog/2020/03/70zovqqaeiph6v1bjoenzl_hn1w.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_c13a702dca784191.png 480w, https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_bac40a4790b954be.png 768w, https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k_hu_5dca5fe2f4405937.png 1400w"
src="https://percona.community/blog/2020/03/a2t8pqil4sxnosey38i-baf3q1k.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_4dd7be60b1fb085f.png 480w, https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_ce4453377035cf40.png 768w, https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo_hu_f674c51bc2f65519.png 1400w"
src="https://percona.community/blog/2020/03/afh_9ofcv7htwplfgomnxejosxo.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_b6dc2671291665d8.png 480w, https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_50ac5b145f9aac0b.png 768w, https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya_hu_aae323b12266ed0.png 1400w"
src="https://percona.community/blog/2020/03/wxxwvcdusefetrgyaxpnnrvxwya.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_7bfa828fc22c8f5f.jpeg 480w, https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_9fdfd762dfead4fb.jpeg 768w, https://percona.community/blog/2020/02/tarantool-storage-1-replica-set_hu_2d7372dc2d11d2a1.jpeg 1400w"
src="https://percona.community/blog/2020/02/tarantool-storage-1-replica-set.jpeg" alt="Storage-1 replicaset" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/02/tarantool-dashboard_hu_67de0265a6a484d5.png 480w, https://percona.community/blog/2020/02/tarantool-dashboard_hu_b12d37875494a62f.png 768w, https://percona.community/blog/2020/02/tarantool-dashboard_hu_b31e84df63588bb3.png 1400w"
src="https://percona.community/blog/2020/02/tarantool-dashboard.png" alt="tarantool dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/02/tarantool-new-instances_hu_9fcf239f61d4ab7.png 480w, https://percona.community/blog/2020/02/tarantool-new-instances_hu_6772a6582983aae2.png 768w, https://percona.community/blog/2020/02/tarantool-new-instances_hu_717751ff073d04ca.png 1400w"
src="https://percona.community/blog/2020/02/tarantool-new-instances.png" alt="Tarantool new instances in dashboard" />&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 sizes="100vw" srcset="https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_b8f60add49f66b39.png 480w, https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_f34721aaf3941891.png 768w, https://percona.community/blog/2020/02/tarantool-new-replicaset_hu_9b7b224c8e92b124.png 1400w"
src="https://percona.community/blog/2020/02/tarantool-new-replicaset.png" alt="Tarantool new replicaset" />&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 src="https://percona.community/blog/2020/01/ricardo-gomez-angel-87vUJY3ntyI-unsplash.jpg" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-1_hu_1c1b623882062453.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-1_hu_4df0ffc42a97dc10.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-1_hu_2e97ee929d5662eb.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-1.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-2_hu_10d09c79448f3e8b.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-2_hu_981f35364b7097dd.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-2_hu_cac33e2989c14f49.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-2.png" alt=" " />&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 src="https://percona.community/blog/2020/01/PMM-Contribute-3.png" alt=" " />&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 src="https://percona.community/blog/2020/01/PMM-Contribute-7-warn.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-5_hu_c8205f9871ed0107.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-5_hu_6214fa658463ed88.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-5_hu_bf49d9cb7dc9b968.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-5.png" alt=" " />&lt;/figure>
&lt;figure>&lt;img src="https://percona.community/blog/2020/01/PMM-Contribute-4.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-6_hu_a24e26e2ae910c77.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-6_hu_129d0bcaabd8198c.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-6_hu_bae6a0276ffc5110.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-6.png" alt=" " />&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 sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_889bfe8a10809cdd.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_e8d54b73856ac009.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-8-lic_hu_564bd6e3f781cf5f.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-8-lic.png" alt=" " />&lt;/figure>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2020/01/PMM-Contribute-9_hu_cf73d2ff4193d390.png 480w, https://percona.community/blog/2020/01/PMM-Contribute-9_hu_e6f3ad12818c865a.png 768w, https://percona.community/blog/2020/01/PMM-Contribute-9_hu_257f945253406da2.png 1400w"
src="https://percona.community/blog/2020/01/PMM-Contribute-9.png" alt=" " />&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 src="https://percona.community/blog/2020/01/PMM-Contribute-10.png" alt=" " />&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 src="https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash.jpg" alt=" " />&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" 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">Operations performed: 24570 Read, 16380 Write, 52352 Other = 93302 Total
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Read 383.91Mb Written 255.94Mb Total transferred 639.84Mb (2.1327Mb/sec)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 136.50 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.0103s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total number of events: 40950
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> total time taken by event execution: 230.0220
&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.03ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> avg: 5.62ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> max: 692.52ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> approx. 95 percentile: 13.96ms
&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): 40950.0000/0.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> execution time (avg/stddev): 230.0220/0.00&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>As you can see the transactions per second between the Western Digital Drive and the KEXIN Drive was more than 12.5% slower. The queries per second between the Western Digital Drive and KEXIN drive were more than 12.5% slower. Even the sysbench showed an extreme difference between the two drives. There is a 13.36ms difference in the 95% percentile. 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-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">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;p>Western Digital:&lt;/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">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;p>With the cost of SSD drives dropping, we can see that the Raspberry Pi 4, 4GB with an SSD drive is a good choice for a small business (or anyone) that needs a good robust database at an affordable price range.&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>&lt;em>Photo by &lt;a href="https://unsplash.com/@enricosottocorna?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Enrico Sottocorna&lt;/a> on &lt;a href="https://unsplash.com/s/photos/berries-spoons?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Wayne Leutwyler</author><category>MySQL</category><category>Open Source Databases</category><category>Percona Server for MySQL</category><media:thumbnail url="https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_bdcf89c101a6bdfa.jpg"/><media:content url="https://percona.community/blog/2020/01/enrico-sottocorna-HOhR-t0yZIU-unsplash_hu_8188425365f5f8cf.jpg" medium="image"/></item><item><title>A First Look at Amazon RDS Proxy</title><link>https://percona.community/blog/2020/01/07/a-first-look-at-amazon-rds-proxy/</link><guid>https://percona.community/blog/2020/01/07/a-first-look-at-amazon-rds-proxy/</guid><pubDate>Tue, 07 Jan 2020 11:45:40 UTC</pubDate><description>At re:Invent in Las Vegas in December 2019, AWS announced the public preview of RDS Proxy, a fully managed database proxy that sits between your application and RDS. The new service offers to “share established database connections, improving database efficiency and application scalability”.</description><content:encoded>&lt;p>At &lt;a href="https://reinvent.awsevents.com/" target="_blank" rel="noopener noreferrer">re:Invent&lt;/a> in Las Vegas in December 2019, &lt;strong>AWS announced the public preview of &lt;a href="https://aws.amazon.com/rds/proxy/" target="_blank" rel="noopener noreferrer">RDS Proxy&lt;/a>&lt;/strong>, a fully managed database proxy that sits between your application and RDS. The new service offers to “&lt;em>share established database connections, improving database efficiency and application scalability”&lt;/em>.&lt;/p>
&lt;p>But one of the benefits that caught my eye is the ability to reduce the downtime in case of an instance failure and a failover. As for the announcement:&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50_hu_fbe2b632fd0d61a4.jpg 480w, https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50_hu_60b8af3720c9d5c0.jpg 768w, https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50_hu_83995111e92d9238.jpg 1400w"
src="https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50.jpg" alt="Photo by Allie Smith on Unsplash" />&lt;/figure>&lt;/p>
&lt;blockquote>
&lt;p>In case of a failure, RDS Proxy automatically connects to a standby database instance while preserving connections from your application and reduces failover times for RDS and Aurora multi-AZ databases by up to 66%"&lt;/p>&lt;/blockquote>
&lt;p>You can read more about the announcement and the new service on the AWS &lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/12/amazon-rds-proxy-available-in-preview/" target="_blank" rel="noopener noreferrer">blog&lt;/a> but as the service is already available in public preview, it is time to give it a try.&lt;/p>
&lt;h2 id="what-does-reduces-failover-times-by-66-mean-and-how-can-we-test-it">What does “reduces failover times by 66%” mean and how can we test it?&lt;/h2>
&lt;p>According to the documentation:&lt;/p>
&lt;blockquote>
&lt;p>“Failovers, as defined by the interval between the detection of the failure on the primary and the resumption of transactions on the standby, typically complete within one to two minutes. Failover time can also be affected by whether large uncommitted transactions must be recovered; the use of adequately large instance types is recommended with Multi-AZ for best results. "&lt;/p>&lt;/blockquote>
&lt;p>So I decided to perform a simple test, using only two terminals, a MySQL client and a while loop in Bash: I wanted to check what happens when I trigger &lt;strong>a forced failover (reboot with failover)&lt;/strong> on a Multi AZ RDS instance running MySQL 5.7.26 behind a RDS Proxy.&lt;/p>
&lt;h3 id="the-simplest-test">The simplest test&lt;/h3>
&lt;p>I created a new proxy &lt;em>“test-proxy”&lt;/em> that pointed to a m5.large Multi AZ &lt;em>“test-rds”&lt;/em> instance. And I set the idle client connection timeout to 3 minutes, a value that should allow us to avoid dropping connections given the expected failover time on the RDS instance.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/12/Screenshot_2019-12-19-RDS-%C2%B7-AWS-Console.png" alt="Creating RDS Proxy" />&lt;/figure>&lt;/p>
&lt;p>And after a few minutes I was ready to go. I started two while loops against the proxy and against the instance, each retrieving current time from 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-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">$ while true; do mysql -s -N -h test-proxy.proxy-cqz****wmlnh.us-east-1.rds.amazonaws.com -u testuser -e "select now()"; sleep 2; 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">$ while true; do mysql -s -N -h test-rds.cqz****wmlnh.us-east-1.rds.amazonaws.com -u testuser -e "select now()"; sleep 2; done&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Acknowledged, this is a pretty basic and limited approach, but one that can quickly provide a feeling of how the RDS proxy performs during a forced failover. &lt;strong>test-rds instance&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">2019-12-16 18:45:48
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:52
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:54
&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>test-proxy proxy&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">2019-12-16 18:45:48
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:52
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:45:54
&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>Which terminal was going to be the winner and have the smallest gap in the time &lt;strong>once I triggered the reboot with failover?&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-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">aws rds reboot-db-instance --db-instance-identifier test-rds --force-failover
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">```Let's see the results. **test-rds instance**```
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:31
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:33
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:49:44
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:49:46
&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>test-proxy proxy&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">(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:31
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:33
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:56
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-12-16 18:47:58
&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>From a delay of 129 seconds for the “test-rds” instance to 21 seconds for the proxy&lt;/strong>, it is quite a significant difference. Even better than the advertised 66%. I performed the test a couple of more times to make sure the result was not a one off, but the numbers are pretty consistent and &lt;strong>the gap was always significant&lt;/strong>.&lt;/p>
&lt;h3 id="main-limitations-and-caveats">Main limitations and caveats&lt;/h3>
&lt;p>As of today, RDS Proxy is in public preview and available for RDS MySQL (MySQL 5.6 and MySQL 5.7) and Aurora MySQL . There is currently no support for RDS PostgreSQL or Aurora PostgreSQL. And it’s important to note: &lt;strong>there is as yet no opportunity to change the instance size or class once the proxy has been created. That means it cannot be used to reduce downtime during a vertical scaling of the instance, which would be one of the main scenarios for the product.&lt;/strong>&lt;/p>
&lt;p>You can still trigger a modifying instance on the Multi AZ RDS but the proxy will then not be able to recover after a scaling operation. It will still be there but will only provide a “MySQL server has gone away” 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">ERROR 2006 (HY000) at line 1: MySQL server has gone away
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 1105 (HY000) at line 1: Unknown error
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 2006 (HY000) at line 1: MySQL server has gone away
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 2006 (HY000) at line 1: MySQL server has gone away
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR 2006 (HY000) at line 1: MySQL server has gone away&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That is actually expected. As per the documentation:&lt;/p>
&lt;blockquote>
&lt;p>“Currently, proxies don’t track any changes to the set of DB instances within an Aurora DB cluster. Those changes include operations such as host replacements, instance renames, port changes, scaling instances up or down, or adding or removing DB instances.”&lt;/p>&lt;/blockquote>
&lt;p>You can find all the current limitations &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html#rds-proxy.limitations" target="_blank" rel="noopener noreferrer">here&lt;/a>.&lt;/p>
&lt;h2 id="what-about-costs">What about costs?&lt;/h2>
&lt;p>Compared to other more convoluted AWS models, &lt;strong>the pricing structure of RDS Proxy is actually &lt;a href="https://aws.amazon.com/rds/proxy/pricing/" target="_blank" rel="noopener noreferrer">simple&lt;/a>&lt;/strong>: you pay a fixed hourly amount ($0.015 in us-east-1) per vCPU of the underlying database instance, regardless of instance class or other configurations. The larger the instance running behind the Proxy, the higher the price.&lt;/p>
&lt;h3 id="how-is-that-going-to-affect-your-overall-rds-costs">How is that going to affect your overall RDS costs?&lt;/h3>
&lt;p>Let’s take two popular instances t3.small (1vCPU) and m5.large (2 vCPU): the cost of the Proxy is about 12 USD and 24 USD per month. That is about 8% on top of cost of the Multi AZ instance for the m5.large, and over 20% for the t3.small.&lt;/p>
&lt;p>Of course, as you are likely preserving connections, you might be able to absorb the cost of the proxy itself by running a smaller instance, but that might not be always the case.&lt;/p>
&lt;p>Note that as per the current documentation, the Amazon RDS Proxy preview was free until the end of 2019 only.&lt;/p>
&lt;p>To recap, &lt;strong>RDS Proxy is a new service by Amazon and still in preview but the results in term of reduced failover times are really promising.&lt;/strong> On top of providing a simpler layer to handle database connections for serverless architectures.&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>&lt;em>–&lt;/em>
&lt;em>Photo Allie Smith on &lt;a href="https://unsplash.com/" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Renato Losio</author><category>renato-losio</category><category>Amazon RDS</category><category>AWS</category><category>aws</category><category>DevOps</category><category>MySQL</category><category>proxy</category><category>RDS</category><category>RDS Proxy</category><media:thumbnail url="https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50_hu_478971ef086a83c5.jpg"/><media:content url="https://percona.community/blog/2019/12/allie-smith-zp-0uEqBwpU-unsplash-50_hu_fa2ec7087352b42.jpg" medium="image"/></item><item><title>Percona Server for MySQL 8.0 – New Data Masking Feature</title><link>https://percona.community/blog/2019/12/13/percona-server-for-mysql-8-0-new-data-masking-feature/</link><guid>https://percona.community/blog/2019/12/13/percona-server-for-mysql-8-0-new-data-masking-feature/</guid><pubDate>Fri, 13 Dec 2019 10:43:14 UTC</pubDate><description>Database administrators are responsible for maintaining the privacy and integrity of data. When the data contains confidential information, your company has a legal obligation to ensure that privacy is maintained. Even so, being able to access the information contained in that dataset, for example for testing or reporting purposes, has great value so what to do? MySQL Enterprise Edition offers data masking and de-identification, so I decided to contribute similar functionality to Percona Server for MySQL. In this post, I provide some background context and information on how to use these new functions in practice.</description><content:encoded>&lt;p>Database administrators are responsible for maintaining the privacy and integrity of data. When the data contains confidential information, your company has a legal obligation to ensure that privacy is maintained. Even so, being able to access the information contained in that dataset, for example for testing or reporting purposes, has great value so what to do? &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/data-masking.html" target="_blank" rel="noopener noreferrer">MySQL Enterprise Edition&lt;/a> offers data masking and de-identification, so I decided to contribute similar functionality to &lt;a href="https://www.percona.com/doc/percona-server/LATEST/security/data-masking.html" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a>. In this post, I provide some background context and information on how to use these new functions in practice.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/12/data-masking-Percona-Server-for-MySQL.jpg" alt="Data Masking in Percona Server for MySQL 8.0.17" />&lt;/figure>&lt;/p>
&lt;h2 id="some-context">Some context&lt;/h2>
&lt;p>One of the most important assets of any company is data. Having good data allows engineers to build better systems and user experiences.&lt;/p>
&lt;p>Even through our most trivial activities, we continuously generate and share great volumes of data. I’m walking down the street and if I take a look at my phone it’s quite straightforward to get recommendations for a place to have lunch. The platform knows that it’s almost lunch time and that I have visited this nearby restaurant, or a similar one, a few times in the past. Sounds cool, right?&lt;/p>
&lt;p>But this process could be more manual than we might think at first. Even if the system has implemented things like AI or Machine Learning, a human will have validated the results; they might have taken a peek to ensure that everything is fine; or perhaps they are developing some new cool feature that must be tested… And this means that someone, somewhere has the ability to access my data. Or your data.&lt;/p>
&lt;p>Now, that is not so great, is it?&lt;/p>
&lt;p>In the last decade or so, governments around the world have taken this challenge quite seriously. They have enforced a series of rules to guarantee that the data is not only safely stored, but also safely used. I’m sure you will have heard terms like PCI, GDPR or HIPAA. They contain mandatory guidelines for how our data can be used, for primary or secondary purposes, and if it can be used at all.&lt;/p>
&lt;h2 id="data-masking-and-de-identification">Data masking and de-identification&lt;/h2>
&lt;p>One of the most basic safeguarding rules is that if the data is to be used for secondary purposes – such as for data analytics – it has to be de-identified in a way that it would make impossible identify the original individual.&lt;/p>
&lt;p>Let’s say that the company ACME is storing employee data.&lt;/p>
&lt;p>We will use the &lt;a href="https://github.com/datacharmer/test_db" target="_blank" rel="noopener noreferrer">example database of employees&lt;/a> that’s freely available.&lt;/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">Employee number
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">First name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Last name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Birth date
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Gender
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Hire date
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Gross salary
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Salary from date
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Salary to date&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We can clearly see that all those fields can be classified as private information. Some of these directly identify the original individual, like employee number or first + last name. Others could be used for indirect identification: I could ask my co-workers their birthday and guess the owner of that data using birth date.&lt;/p>
&lt;p>So, here is where de-identification and data-masking come into play. But what are the differences?&lt;/p>
&lt;p>&lt;strong>De-identification&lt;/strong> transforms the original data into something different that could look more or less real. For example, I could de-identify birth date and get a different date.&lt;/p>
&lt;p>However, this method would make that information unusable if I want to see the relationship between salary and employee’s age.&lt;/p>
&lt;p>On the other hand, &lt;strong>data-masking&lt;/strong> transforms the original data leaving some part untouched. I could mask birth date replacing the month and day for January first. That way, the year would be retained and that would allow us to identify that salary–employee’s age relationship.&lt;/p>
&lt;p>Of course, if the dataset I’m working with is not big enough, certain methods of data-masking would be inappropriate as I could still deduce who the data belonged to.&lt;/p>
&lt;h2 id="mysql-data-masking">MySQL data masking&lt;/h2>
&lt;p>&lt;strong>Oracle’s MySQL Enterprise Edition&lt;/strong> offers a &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/data-masking.html" target="_blank" rel="noopener noreferrer">de-identification and data-masking solution for MySQL&lt;/a>, using a flexible set of functions that cover most of our needs.&lt;/p>
&lt;p>&lt;strong>Percona Server for MySQL 8.0.17&lt;/strong> introduces that functionality as &lt;a href="https://www.percona.com/doc/percona-server/LATEST/security/data-masking.html" target="_blank" rel="noopener noreferrer">an open source plugin&lt;/a>, and is compatible with Oracle’s implementation. You no longer need to code slow and complicated stored procedures to achieve data masking, and you can migrate the processes that were written for the MySQL Enterprise Edition to Percona Server for MySQL. Go grab a cup of coffee and contribute something cool to the community with all that time you have got back. ☺&lt;/p>
&lt;h2 id="in-the-lab">In the lab&lt;/h2>
&lt;p>Put on your thinking cap and let’s see how it works.&lt;/p>
&lt;p>First we need an instance of Percona MySQL Server 8.0.17 or newer. I think containers are the most flexible way to test new stuff so I will be using that, but you could use a virtual server or just a traditional setup. Let’s download the latest version of Percona MySQL Server in a ready to run 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 pull percona:8.0.17-1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Eventually that command should work but sadly, Percona hadn’t built this version of the docker image when this article was written. Doing it yourself is quite simple, though, and by the time you read this it will likely be already there.&lt;/p>
&lt;p>Once in place, Running an instance of Percona MySQL Server has never been so easy:&lt;/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 run --name ps -e MYSQL_ROOT_PASSWORD=secret -d percona:8.0.17-8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We’ll logon to the new 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-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">docker exec -ti ps mysql -u root -p&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now is the time to download the test database employees from &lt;a href="https://github.com/datacharmer/test_db" target="_blank" rel="noopener noreferrer">GitHub&lt;/a> and load it into our Percona Server. You can follow the official instructions in the project page.&lt;/p>
&lt;p>Next step is to enable the data de-identification and masking feature. Installing the data masking module in Percona MySQL Server is easier than in Oracle.&lt;/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> INSTALL PLUGIN data_masking SONAME 'data_masking.so';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 rows affected (0.06 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This automatically defines a set of global functions in our MySQL instance, so we don’t need to do anything else.&lt;/p>
&lt;h3 id="a-new-concept-dictionaries">A new concept: Dictionaries&lt;/h3>
&lt;p>Sometimes we will like to generate new data selecting values from a predefined collection. For example we could want to have first name  values that are really first names and not a random alphanumeric. This will make our masked data looks real, and it’s perfect for creating demo or QA environments.&lt;/p>
&lt;p>For this task we have &lt;strong>dictionaries&lt;/strong>. They are nothing more than text files containing a value per line that are loaded into MySQL memory. You need to be aware that the contents of the file are fully loaded into memory and that the dictionary only exists while MySQL is running. So keep this in mind before loading any huge file or after restarting the instance.&lt;/p>
&lt;p>For our lab we will load two dictionaries holding first and last names. You can use these files or create different ones: &lt;a href="https://raw.githubusercontent.com/philipperemy/name-dataset/master/names_dataset/first_names.all.txt" target="_blank" rel="noopener noreferrer">first names&lt;/a> and &lt;a href="https://raw.githubusercontent.com/philipperemy/name-dataset/master/names_dataset/last_names.all.txt" target="_blank" rel="noopener noreferrer">last names&lt;/a>&lt;/p>
&lt;p>Store the files in a folder of your database server (or container) readable by the mysqld  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-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">wget https://raw.githubusercontent.com/philipperemy/name-dataset/master/names_dataset/first_names.all.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker cp first_names.all.txt ps:/tmp/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget https://raw.githubusercontent.com/philipperemy/name-dataset/master/names_dataset/last_names.all.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker cp last_names.all.txt ps:/tmp/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Once the files are in our server we can map them as MySQL dictionaries.&lt;/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 gen_dictionary_load('/tmp/first_names.all.txt', 'first_names');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| gen_dictionary_load('/tmp/first_names.all.txt', 'first_names') |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Dictionary load success                                        |
&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.04 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 gen_dictionary_load('/tmp/last_names.all.txt', 'last_names');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| gen_dictionary_load('/tmp/last_names.all.txt', 'last_names') |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Dictionary load success                                      |
&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.03 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="masking-some-data">Masking some data&lt;/h3>
&lt;p>Now let’s take another look at our employees 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-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> show columns from employees;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------+---------------+------+-----+---------+-------+
&lt;/span>&lt;/span>&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">+------------+---------------+------+-----+---------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| emp_no     | int(11)   | NO | PRI | NULL    | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| birth_date | date          | NO | | NULL   | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| first_name | varchar(14)   | NO | | NULL   | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| last_name  | varchar(16)   | NO | | NULL   | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| gender     | enum('M','F') | NO   | | NULL   | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| hire_date  | date   | NO | | 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>Ok, it’s very likely we will want to de-identify everything in this table. You can apply different methods to achieve your security requirements, but I will create a view with the following transformations:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>emp_no&lt;/strong>: get a random value from 900.000.000 to 999.999.999&lt;/li>
&lt;li>&lt;strong>birth_date&lt;/strong>: set it to January 1st of the original year&lt;/li>
&lt;li>&lt;strong>first_name&lt;/strong>: set a random first name from a list of names that we have in a text file&lt;/li>
&lt;li>&lt;strong>last_name&lt;/strong>: set a random last name from a list of names that we have in a text file&lt;/li>
&lt;li>&lt;strong>gender&lt;/strong>: no transformation&lt;/li>
&lt;li>&lt;strong>hire_date&lt;/strong>: set it to January 1st of the original year&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">CREATE VIEW deidentified_employees
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  gen_range(900000000, 999999999) as emp_no,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  makedate(year(birth_date), 1) as birth_date,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  gen_dictionary('first_names') as first_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  gen_dictionary('last_names') as last_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  gender,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">  makedate(year(hire_date), 1) as hire_date
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM employees;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Let’s check how the data looks in our de-identified view.&lt;/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">mysql> SELECT * FROM employees LIMIT 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">| emp_no | birth_date | first_name | last_name | gender | hire_date  |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+------------+------------+-----------+--------+------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 | 1953-09-02 | Georgi     | Facello | M | 1986-06-26 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10002 | 1964-06-02 | Bezalel    | Simmel | F | 1985-11-21 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10003 | 1959-12-03 | Parto      | Bamford | M | 1986-08-28 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10004 | 1954-05-01 | Chirstian  | Koblick | M | 1986-12-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10005 | 1955-01-21 | Kyoichi    | Maliniak | M | 1989-09-12 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10006 | 1953-04-20 | Anneke     | Preusig | F | 1989-06-02 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10007 | 1957-05-23 | Tzvetan    | Zielinski | F | 1989-02-10 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10008 | 1958-02-19 | Saniya     | Kalloufi | M | 1994-09-15 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10009 | 1952-04-19 | Sumant     | Peac | F | 1985-02-18 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10010 | 1963-06-01 | Duangkaew  | Piveteau | F | 1989-08-24 |
&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 rows 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> SELECT * FROM deidentified_employees LIMIT 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">| emp_no    | birth_date | first_name | last_name     | gender | hire_date |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+------------+------------+---------------+--------+------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 930277580 | 1953-01-01 | skaidrīte  | molash | M | 1986-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 999241458 | 1964-01-01 | grasen     | cessna | F | 1985-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 951699030 | 1959-01-01 | imelda     | josephpauline | M | 1986-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 985905688 | 1954-01-01 | dunc       | burkhardt | M | 1986-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 923987335 | 1955-01-01 | karel      | wanamaker | M | 1989-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 917751275 | 1953-01-01 | mikrut     | allee | F | 1989-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 992344830 | 1957-01-01 | troyvon    | muma | F | 1989-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 980277046 | 1958-01-01 | aliziah    | tiwnkal | M | 1994-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 964622691 | 1952-01-01 | dominiq    | legnon | F | 1985-01-01 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 948247243 | 1963-01-01 | sedale     | tunby | F | 1989-01-01 |
&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 rows in set (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The data looks quite different, but remains good enough to apply some analytics and get meaningful results. Let’s de-identify the table salaries  this time.&lt;/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">mysql> show columns from salaries;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+---------+------+-----+---------+-------+
&lt;/span>&lt;/span>&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">+-----------+---------+------+-----+---------+-------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| emp_no    | int(11) | NO   | PRI | NULL |       |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| salary    | int(11) | NO   | | NULL |       |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| from_date | date    | NO | PRI | NULL |       |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| to_date   | date | NO   | | 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>We could use 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-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 VIEW deidentified_salaries
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gen_range(900000000, 999999999) as emp_no,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gen_range(40000, 80000) as salary,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mask_inner(date_format(from_date, '%Y-%m-%d'), 4, 0) as from_date,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mask_outer(date_format(to_date, '%Y-%m-%d'), 4, 2, '0') as to_date
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM salaries;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>We are using again the function gen_range . For the dates this time we are using the very flexible functions mask_inner  and mask_outer  that replace some characters in the original string. Let’s see how the data looks now.&lt;/p>
&lt;blockquote>
&lt;p>In a real life exercise we would like to have the same values for emp_no across all the tables to keep referential integrity. This is where I think the original MySQL data-masking plugin falls short, as we don’t have deterministic functions using the original value as seed.&lt;/p>&lt;/blockquote>
&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">mysql> SELECT * FROM salaries LIMIT 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">| emp_no | salary | from_date  | to_date |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------+--------+------------+------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  60117 | 1986-06-26 | 1987-06-26 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  62102 | 1987-06-26 | 1988-06-25 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  66074 | 1988-06-25 | 1989-06-25 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  66596 | 1989-06-25 | 1990-06-25 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  66961 | 1990-06-25 | 1991-06-25 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  71046 | 1991-06-25 | 1992-06-24 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  74333 | 1992-06-24 | 1993-06-24 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  75286 | 1993-06-24 | 1994-06-24 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  75994 | 1994-06-24 | 1995-06-24 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|  10001 |  76884 | 1995-06-24 | 1996-06-23 |
&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 rows 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> SELECT * FROM deidentified_salaries LIMIT 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">| emp_no    | salary | from_date  | to_date |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------+--------+------------+------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 929824695 | 61543  | 1986XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 954275265 | 63138  | 1987XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 948145700 | 53448  | 1988XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 937927997 | 54704  | 1989XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 978459605 | 78179  | 1990XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 993464164 | 75526  | 1991XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 946692434 | 51788  | 1992XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 979870243 | 54807  | 1993XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 958708118 | 70647  | 1994XXXXXX | 0000-06-00 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 945701146 | 76056  | 1995XXXXXX | 0000-06-00 |
&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 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="clean-up">Clean-up&lt;/h3>
&lt;p>Remember that when you’re done, you can free up memory by removing the dictionaries. Restarting the instance will also remove the dictionaries.&lt;/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">mysql> SELECT gen_dictionary_drop('first_names');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| gen_dictionary_drop('first_names') |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Dictionary removed                 |
&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql> SELECT gen_dictionary_drop('last_names');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| gen_dictionary_drop('last_names') |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Dictionary removed                 |
&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>If you use the MySQL data-masking plugin to define different levels of access to the data, remember that you will need to load the dictionaries each time the instance is restarted. With this usage, for example, you could control the data that someone in support has access to, very much like a bargain-basement virtual private database solution. (I’m not proposing this for production systems!)&lt;/p>
&lt;h2 id="other-de-identification-and-masking-functions">Other de-identification and masking functions&lt;/h2>
&lt;p>Percona Server for MySQL Data-Masking includes more functions that the ones we’ve seen here. We have specialized functions for Primary Account Numbers (PAN), Social Security Numbers (SSN), phone numbers, e-Mail addresses… And also generic functions that will allow us to de-identify types without a specialized method. Being an open source plugin it should be quite easy to implement any additional methods and contribute it to the broader community.&lt;/p>
&lt;h2 id="next-steps">Next Steps&lt;/h2>
&lt;p>Using these functions we can de-identify and mask any existing dataset. But if you are populating a lower level environment using production data you would want to store the transformed data only. To achieve this you could choose between various options.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Small volumes of data&lt;/strong>: use “de-identified” views to export the data and load into a new database using mysqldump or mysqlpump.&lt;/li>
&lt;li>&lt;strong>Medium volumes of data&lt;/strong>: Clone the original database and de-identify locally the data using updates.&lt;/li>
&lt;li>&lt;strong>Large volumes of data option one&lt;/strong>: using replication, create a master -> slave chain with STATEMENT binlog format and define triggers de-identifying the data on the slave. Your master can be a slave to the master (using log_slave_updates), so you don’t need to run your primary master in STATEMENT mode.&lt;/li>
&lt;li>&lt;strong>Large volumes of data option two&lt;/strong>: using multiplexing in &lt;a href="https://www.proxysql.com/" target="_blank" rel="noopener noreferrer">ProxySQL&lt;/a>, configure ProxySQL to send writes to a clone server where you have defined triggers to de-identify the data.&lt;/li>
&lt;/ul>
&lt;h2 id="future-developments">Future developments&lt;/h2>
&lt;p>While de-identifying complex schemas we could find that, for example, the name of a person is stored in multiple tables (de-normalized tables). In this case, these functions would generate different names and the resulting data will look broken. You can solve this using a variant of the dictionary functions that will obtain the value based on the original value and passed as parameter:&lt;/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">gen_dictionary_deterministic('Francisco', 'first_names')&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This not-yet-available function would always return the same value using that dictionary file, but in such a way that the de-identification cannot be reversed. Oracle doesn’t currently support this, so we will expand Percona Data-Masking plugin to introduce this as a unique feature. However, that will be in another contribution, so stay tuned for more exciting changes to Percona Server for MySQL Data Masking.&lt;/p>
&lt;p>&lt;em>–&lt;/em>&lt;/p>
&lt;p>&lt;em>Image: Photo by &lt;a href="https://unsplash.com/@finan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Finan Akbar&lt;/a> on &lt;a href="https://unsplash.com/s/photos/mask?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p>
&lt;p>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 (although in this case, of course, we have tested the data masking feature incorporated into Percona Server for MySQL 8.0/17, just not the examples in this blog). 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;/p></content:encoded><author>Francisco Miguel Biete Banon</author><category>data obfuscation</category><category>data privacy</category><category>identity protection</category><category>Intermediate Level</category><category>MySQL</category><category>Percona Server for MySQL</category><media:thumbnail url="https://percona.community/blog/2019/12/data-masking-Percona-Server-for-MySQL_hu_579bb5525b9e0b33.jpg"/><media:content url="https://percona.community/blog/2019/12/data-masking-Percona-Server-for-MySQL_hu_3d1019fef5fce818.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: Test Like a Boss</title><link>https://percona.community/blog/2019/09/25/percona-live-europe-presents-test-like-a-boss/</link><guid>https://percona.community/blog/2019/09/25/percona-live-europe-presents-test-like-a-boss/</guid><pubDate>Wed, 25 Sep 2019 06:31:58 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/09/dbdeployer.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;p>My first talk is a tutorial &lt;em>Testing like a boss: Deploy and Test Complex Topologies With a Single Command&lt;/em>, scheduled at &lt;a href="https://www.percona.com/live-agenda" target="_blank" rel="noopener noreferrer">Percona Live Europe in Amsterdam&lt;/a> on September 30th at 13:30.&lt;/p>
&lt;p>My second talk is &lt;em>Amazing sandboxes with dbdeployer&lt;/em> scheduled on October 1st at 11:00. It is the same topic as the tutorial, but covers a narrow set of features, all in the *amazing* category.&lt;/p>
&lt;p>The tutorial introduces a challenging topic, because when people hear &lt;em>testing&lt;/em>, they imagine a troop of monkeys fiddling with a keyboard and a mouse, endlessly repeating a boring task. What I want to show is that testing is a creative activity and, with the right tools and mindset, it could be exciting and rewarding. During my work as a quality assurance engineer, I have always seen a boring task as an opportunity to automate. &lt;a href="https://github.com/datacharmer/dbdeployer" target="_blank" rel="noopener noreferrer">dbdeployer&lt;/a>, the tool at the heart of my talk, was born from one such challenge. While working as a MySQL consultant, I realized that every customer was using a different version of MySQL. When they had a problem, I couldn’t just use the latest and greatest version and recommend they upgrade: almost nobody wanted to even consider that, and I can see the point. Sometimes, upgrading is a huge task that should be planned appropriately, and not done as a troubleshooting measure. If I wanted to assist my customers, I had to install their version, reproduce the problem, and propose a solution. After installing and reinstalling several versions of MySQL manually, and juggling dozens of options to use the right version for the right task, I decided to make a tool for that purpose. That was in 2006, and since then the tool has evolved to handle the newest features of MySQL, was rewritten almost two years ago, and now is been adopted by several categories of database professionals: developers, DBAs, support engineers, and quality assurance engineers.&lt;/p>
&lt;p>Looking at the user base of dbdeployer, it’s easy to reconsider the concept of &lt;em>testing&lt;/em>: it could be exploring the latest MySQL or Percona Server release, or a building a sample Group Replication or Percona XtraDB Cluster, or comparing a given setup across different versions of MySQL. Still unconvinced? Read on!&lt;/p>
&lt;h3 id="whats-the-catch-what-do-attendees-get-from-attending">What’s the catch? What do attendees get from attending?&lt;/h3>
&lt;p>In addition to opening their eyes to the beauty of testing, this tutorial will show several activities that a normal user would consider difficult to perform, time consuming, and error prone.&lt;/p>
&lt;p>The key message of this presentation is that users should focus on &lt;strong>what&lt;/strong> to do, and leave the details of &lt;strong>how&lt;/strong> to perform the task to the tools at their disposal. The examples will show that you can deploy complicated scenarios with just a few commands, usually in less than one minute, sometimes in less than ten seconds, and then spend your time with the real task, which is exploring, trying a particular feature, proving a point, and not doing manually and with errors what the tool can do for you quickly and precisely.&lt;/p>
&lt;p>Some examples to water your mouth: you can deploy group replication in less than 30 seconds. And what about deploying two groups and running asynchronous replication between them? Even if you have done this before, this is a task that takes you quite a while. dbdeployer can run the whole setup (two clusters in group replication + asynchronous replication on top of it) in less than one minute. How about testing the new &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/clone-plugin.html" target="_blank" rel="noopener noreferrer">clone plugin?&lt;/a> You can do it in a snap using dbdeployer as &lt;a href="http://blog.wl0.org/2019/09/mysql-8-0-17-cloning-is-now-much-easier/" target="_blank" rel="noopener noreferrer">demonstrated recently by Simon Mudd&lt;/a> , which proves the point that having the right tools makes your experiments easier.&lt;/p>
&lt;p>Another example? MySQL upgrade: dbdeployer can run a server upgrade for you faster than you can say “blueberry muffin” or maybe not that fast, but surely faster than reading the manual and following the instructions.&lt;/p>
&lt;h3 id="what-else-is-in-store-at-perconalive-what-will-i-do-apart-from-charming-the-attendees">What else is in store at PerconaLive? What will I do apart from charming the attendees?&lt;/h3>
&lt;p>Percona Live Amsterdam is chock-full of good talks. I know because I was part of the review committee that has examined hundreds of proposals, and painfully approved only a portion of them. Things that I look forward to:&lt;/p>
&lt;ul>
&lt;li>The &lt;em>InnoDB Cluster tutorial&lt;/em> on Monday. Although I have seen this talk several times, the cluster has been improved continuously, and it is useful to see it in action. Besides, &lt;a href="https://lefred.be/" target="_blank" rel="noopener noreferrer">Lefred’s&lt;/a> style of presentation is so engaging that I enjoy it every time.&lt;/li>
&lt;li>Jeremy Cole’s take on Google Cloud, on Tuesday afternoon. Jeremy has been at the top of the database game for long time, and his views are always stimulating.&lt;/li>
&lt;li>&lt;em>Backing up Wikipedia&lt;/em>, with Jaime Crespo and Manuel Arostegui. Seeing how big deployments are dealt with is a sobering experience, which I highly recommend to newcomers and experts alike.&lt;/li>
&lt;li>&lt;em>ClickHouse materialized views&lt;/em>, with Robert Hodges of Altinity. You may not be thrilled about the topic, but the speaker is a guarantee. Robert has been working with databases for several decades, and he knows his way around big data and difficult problems to solve. Looking forward to learning something new here.&lt;/li>
&lt;/ul>
&lt;p>There are many more talks that I encourage you to peruse in &lt;a href="https://www.percona.com/live-agenda" target="_blank" rel="noopener noreferrer">the agenda&lt;/a>.&lt;/p>
&lt;p>As usual, the best part of the conference is networking in the intervals and around the venue before and after the event. This is where the best morsels of knowledge land with serendipity in my plate. See you soon!&lt;/p>
&lt;p>If you haven’t yet &lt;a href="https://www.percona.com/live-registration" target="_blank" rel="noopener noreferrer">registered&lt;/a>, then you are invited to use the code &lt;strong>CMESPEAK-GIUSEPPE&lt;/strong> for a 20% discount.
&lt;figure>&lt;img src="https://percona.community/blog/2019/09/giuseppe-maxia-two-talks.jpg" alt=" " />&lt;/figure> &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>Giuseppe Maxia</author><category>conferences</category><category>dbdeployer</category><category>Events</category><category>MySQL</category><category>testing</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2019/09/dbdeployer_hu_e5464b33ecd5e5bd.jpg"/><media:content url="https://percona.community/blog/2019/09/dbdeployer_hu_1d2451229773b4bd.jpg" medium="image"/></item><item><title>Are your Database Backups Good Enough?</title><link>https://percona.community/blog/2019/09/20/are-your-database-backups-good-enough/</link><guid>https://percona.community/blog/2019/09/20/are-your-database-backups-good-enough/</guid><pubDate>Fri, 20 Sep 2019 15:32:00 UTC</pubDate><description>In the last few years there have been several examples of major service problems affecting businesses data: outages causing data inconsistencies; unavailability or data loss, and worldwide cyberattacks encrypting your files and asking for a ransom.</description><content:encoded>&lt;p>In the last few years there have been several examples of major service problems affecting businesses data: outages causing data inconsistencies; unavailability or data loss, and &lt;a href="https://en.wikipedia.org/wiki/WannaCry_ransomware_attack" target="_blank" rel="noopener noreferrer">worldwide cyberattacks encrypting your files and asking for a ransom&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/08/this_is_fine.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>Database-related incidents are a very common industry issue- even if the root cause is not the database system itself. No matter if your main relational system is MySQL, MariaDB, PostgresQL or AWS Aurora -there will be a time where you will need to make use of backups to recover to a previous state. And when that happens it will be the worst time to realize that your backup system hadn’t been working for months, or testing for the first time a cluster-wide recovery.&lt;/p>
&lt;h2 id="forget-about-the-backups-it-is-all-about-recovery">Forget about the backups, it is all about recovery!&lt;/h2>
&lt;p>Let me be 100% clear: the question is not &lt;strong>IF&lt;/strong> data incidents like those can happen to you, but &lt;strong>WHEN&lt;/strong> it will happen and &lt;strong>HOW&lt;/strong> you are prepared to respond to them. It could be a bad application deploy, an external breach, a disgruntled employee, a hardware failure, a provider problem, ransomware infection or a network failure,… Your relational data will eventually get lost, corrupted or in an inconsistent state, and “I have backups” will not be good enough. Recovery plans and tools have to be in place and in a healthy state.&lt;/p>
&lt;p>As the only 2 Site Reliability Engineers in charge of the Database Layer of &lt;a href="https://www.wikipedia.org/" target="_blank" rel="noopener noreferrer">Wikipedia&lt;/a> and other projects at the &lt;a href="https://wikimediafoundation.org/" target="_blank" rel="noopener noreferrer">Wikimedia Foundation&lt;/a>, &lt;a href="https://www.linkedin.com/in/manuel-arostegui-b977141/" target="_blank" rel="noopener noreferrer">Manuel&lt;/a> and I grew worried on how to improve both our existing data recovery strategy and provisioning systems. We have the responsibility to make sure that free knowledge contributed by &lt;a href="https://stats.wikimedia.org/v2/#/all-projects" target="_blank" rel="noopener noreferrer">millions of volunteers around the world&lt;/a> keeps being available for future generations. As a colleague of us once said- no worries, we “only” are in charge of maintaining &lt;a href="https://en.wikipedia.org/wiki/Encyclopedia_Galactica" target="_blank" rel="noopener noreferrer">the (probably) most valuable collaborative database ever created&lt;/a> in the history of mankind! :-D&lt;/p>
&lt;p>Among the two of us we handle over &lt;strong>half a petabyte of relational data&lt;/strong> &lt;a href="https://grafana.wikimedia.org/d/000000278/mysql-aggregated?orgId=1&amp;var-dc=eqiad%20prometheus%2Fops&amp;var-group=All&amp;var-shard=All&amp;var-role=All" target="_blank" rel="noopener noreferrer">over hundreds of instances and servers&lt;/a>, and manual work is off-limits to be efficient. Unlike other popular Internet services, we not only store metadata in MariaDB databases, &lt;strong>we also store all content&lt;/strong> (Wikitext).&lt;/p>
&lt;ul>
&lt;li>We needed a system that was incredibly &lt;strong>flexible&lt;/strong> - so it worked for both large Wiki databases (like the many terabytes of the &lt;a href="https://en.wikipedia.org/wiki/Special:Statistics" target="_blank" rel="noopener noreferrer">English Wikipedia&lt;/a>), but also for small but important internal database services such as our &lt;a href="https://phabricator.wikimedia.org/" target="_blank" rel="noopener noreferrer">bug tracker&lt;/a> -.&lt;/li>
&lt;li>&lt;strong>fast&lt;/strong> - able to recover data saturating our &lt;a href="https://wikitech.wikimedia.org/wiki/Network_design" target="_blank" rel="noopener noreferrer">10Gbit network&lt;/a> -,&lt;/li>
&lt;li>&lt;strong>granular&lt;/strong> - being able to recover 1 row or an entire instance, to 1 server or an entire cluster; at any arbitrary point in the past-&lt;/li>
&lt;li>and &lt;strong>reliable&lt;/strong> - low rate of failure, but when it failed it &lt;a href="https://docs.honeycomb.io/learning-about-observability/intro-to-observability/" target="_blank" rel="noopener noreferrer">should be detected immediately&lt;/a>, and not when it is too late.&lt;/li>
&lt;li>The system had to use exclusively &lt;strong>free (open source) software&lt;/strong> and be published itself with a &lt;a href="https://en.wikipedia.org/wiki/Free_software_license" target="_blank" rel="noopener noreferrer">free license&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>We ended up with something like this (simplified view :-P):&lt;/p>
&lt;p>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2019/08/Database_backups_overview.svg__hu_ff40939f6450b98e.jpg 480w, https://percona.community/blog/2019/08/Database_backups_overview.svg__hu_ad0bd2abf140e58.jpg 768w, https://percona.community/blog/2019/08/Database_backups_overview.svg__hu_7194b7b9a7d3c6c7.jpg 1400w"
src="https://percona.community/blog/2019/08/Database_backups_overview.svg_.jpg" alt="Workflow of backups and recovery at the Wikimedia Foundation" />&lt;/figure>
&lt;em>A wonderful example of “programmer art”&lt;/em>
Like any application, a recovery system is never complete. However after a year of planning, developing and deploying our solution, we are ready to share what we have finished so far to people outside of our organization.&lt;/p>
&lt;h2 id="our-presentation-at-percona-live-europe-2019">Our Presentation at Percona Live Europe 2019&lt;/h2>
&lt;p>A single blog post is not enough to tell the whole story of how we reached the current state – that is why &lt;strong>we are going to present the work at the &lt;a href="https://www.percona.com/live-info" target="_blank" rel="noopener noreferrer">Percona Live Europe 2019 conference&lt;/a>&lt;/strong> which will take place 29 September–2 October in Amsterdam. We will introduce what was the problem we wanted to solve, our design philosophy, existing tooling used and backup methods, backups checking, recovery verification and general automation. You will be able to compare with your own setup and ask questions about why we chose certain paths, based on our experience.&lt;/p>
&lt;p>What we have setup may not be perfect, and may not work for you- your needs will be different, as well as your environment. However I expect our presentation will inspire you to design and setup better recovery systems in the future.&lt;/p>
&lt;p>See you in Amsterdam! And if you haven’t yet registered, then you are invited to use the code CMESPEAK-JAIME for a 20% discount.&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>Jaime Crespo</author><category>amsterdam</category><category>automation</category><category>backups</category><category>database</category><category>Events</category><category>InnoDB</category><category>mariabackup</category><category>mydumper</category><category>MySQL</category><category>MySQL</category><category>Percona Live 2019</category><category>perconalive</category><category>recovery</category><category>wikimedia</category><category>wikipedia</category><category>xtrabackup</category><media:thumbnail url="https://percona.community/blog/2019/08/this_is_fine_hu_8363d78ea07c7bbb.jpg"/><media:content url="https://percona.community/blog/2019/08/this_is_fine_hu_b454851bd219dc71.jpg" medium="image"/></item><item><title>Percona Live Europe '19: MongoDB 4.2</title><link>https://percona.community/blog/2019/09/18/percona-live-europe-19-mongodb-4-2/</link><guid>https://percona.community/blog/2019/09/18/percona-live-europe-19-mongodb-4-2/</guid><pubDate>Wed, 18 Sep 2019 15:22:25 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/09/percona-live-europe2019.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;p>It’s all about MongoDB® 4.2 this time. MongoDB 4.2 released like a month ago (still newborn) and I am going to cover what’s new in three different areas: sharding, indexing, and the aggregation framework. I can promise you this, there are a lot of new features and improvements in MongoDB 4.2 and I am thrilled to present those to you. Join me at &lt;a href="https://www.percona.com/live-agenda" target="_blank" rel="noopener noreferrer">Percona Live Europe&lt;/a>, and discover how distributed transactions, wildcard indexes and materialized views (plus many other new features) actually work and fit on your workload.&lt;/p>
&lt;h2 id="this-talk-is-for-you-if">This talk is for you if…&lt;/h2>
&lt;p>… you are actively working with MongoDB, either as a DBA/SRE or a Developer. I am confident that you will love the new features and you would like to adopt them straight away after the presentation.&lt;/p>
&lt;p>If you are not working with MongoDB or you have never heard about MongoDB before, come join us and check if the 4.2 new features fit your needs. Maybe MongoDB 4.2 has the answer to a challenge you are currently facing with your existing datastore.&lt;/p>
&lt;h2 id="other-presentations-im-looking-forward-to">Other presentations I’m looking forward to…&lt;/h2>
&lt;p>I wish I could be James Arthur Madrox (the Multiple Man) and attend all talks. I am going to attend all MongoDB related talks, as all the Mongo topics are great this year. I will also try to attend as many Postgres talks as I can, I am very curious to find out how the Percona distribution for Postgres will make my DBA life easier. Keynotes and tutorials are also a must.&lt;/p>
&lt;p>And last but not least, Percona Europe returns to &lt;a href="https://www.percona.com/live-info" target="_blank" rel="noopener noreferrer">Amsterdam&lt;/a>!!!&lt;/p>
&lt;h4 id="more-about-percona-live-europe-2019">More about Percona Live Europe 2019&lt;/h4>
&lt;p>Antonios is presenting two talks at Percona Live Europe 2019: New Indexing and Aggregation Pipeline Capabilities in MongoDB 4.2 and What’s New on Sharding in MongoDB 4.2. He was also an active member of the community paper selection committee (thank you!)&lt;/p>
&lt;p>You can &lt;a href="https://www.percona.com/live-agenda" target="_blank" rel="noopener noreferrer">download a full schedule from the agenda page&lt;/a> and if you’d like to hear these talks, &lt;a href="https://www.percona.com/live-registration" target="_blank" rel="noopener noreferrer">register&lt;/a> with CMESPEAK-ANTONIOS for a 20% discount!&lt;/p></content:encoded><author>Antonios Giannopoulos</author><category>Events</category><category>MongoDB</category><category>Percona Live Europe 2019</category><media:thumbnail url="https://percona.community/blog/2019/09/percona-live-europe2019_hu_fe2c0816585a7259.jpg"/><media:content url="https://percona.community/blog/2019/09/percona-live-europe2019_hu_9b866fc779a2a97a.jpg" medium="image"/></item><item><title>Minimalist Tooling for MySQL/MariaDB DBAs</title><link>https://percona.community/blog/2019/08/14/minimalist-tooling-for-mysql-mariadb-dbas/</link><guid>https://percona.community/blog/2019/08/14/minimalist-tooling-for-mysql-mariadb-dbas/</guid><pubDate>Wed, 14 Aug 2019 14:21:10 UTC</pubDate><description>In my roles as a DBA at various companies, I generally found the tooling to be quite lacking. Everything from metrics collection, alerting, backup management; they were either missing, incomplete or implemented poorly. DBA-Tools was born from a desire to build backup tools that supported my needs in smaller/non-cloud environments. As BASH is easily the most common shell available out there on systems running MySQL® or MariaDB®, it was an easy choice.</description><content:encoded>&lt;p>In my roles as a DBA at various companies, I generally found the tooling to be quite lacking. Everything from metrics collection, alerting, backup management; they were either missing, incomplete or implemented poorly. &lt;a href="http://gitlab.com/gwinans/dba-tools" target="_blank" rel="noopener noreferrer">DBA-Tools&lt;/a> was born from a desire to build backup tools that supported my needs in smaller/non-cloud environments. As BASH is easily the most common shell available out there on systems running MySQL® or MariaDB®, it was an easy choice.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/08/dba-tools-minimalist-mysql-tooling.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;h2 id="how-dba-tools-came-to-be">How DBA-Tools came to be&lt;/h2>
&lt;p>While rebuilding my home-lab two years ago, I decided I wanted some simple tools for my database environment. Being a fan of NOT re-inventing the wheel, I thought I would peruse GitHub and Gitlab to see what others have put together. Nothing I saw looked quite like what I wanted. They all hit one or more of the checkboxes I wanted, but never all of them.&lt;/p>
&lt;p>My checklist when searching for tools included the following features:&lt;/p>
&lt;ul>
&lt;li>Extendable&lt;/li>
&lt;li>Configurable&lt;/li>
&lt;li>User Friendly&lt;/li>
&lt;li>Easy-to-Read&lt;/li>
&lt;/ul>
&lt;p>The majority of scripts I found were contained within a single file and not easy to extend. They were universally easy-to-use. My subjective requirement for code quality simply was not met. When I considered what kits were already available to me against the goal I had in mind, I came to the only reasonable conclusion I could muster:&lt;/p>
&lt;p>I would build my own tools!&lt;/p>
&lt;h2 id="a-trip-down-release-lane-and-publicity">A trip down release lane and publicity&lt;/h2>
&lt;p>DBA-Tools was designed to be simple, extendible and configurable. I wanted my kit to have very few external dependencies. BASH was the shell I chose for implementation and I grew my vision from there. At the most fundamental level, I enjoy simplicity. I consider procedural programming to be just that – simple. This, thus far, remains my guiding philosophy with these tools.&lt;/p>
&lt;p>My first public release was on July 7th, 2019. The scripts only did single full backups and most of the secondary scripts only worked with MariaDB. I posted about it in one of the MySQL Slack groups. The tools were written for my lab use and, while I hoped others would find my offering useful, the lack of noticeable response did not bother me.&lt;/p>
&lt;p>The second release, 22 days later, marked full incremental support and ensured all the secondary scripts supported MySQL and MariaDB. I decided to call this one 2.0.0 and posted it again. I received my first “support” email that day, which spurred me to create better documentation.&lt;/p>
&lt;p>Later, I found out that Peter Zaitsev posted about the tools I wrote on his Twitter and LinkedIn pages on August 11th 2019. I can’t say thank you enough – I didn’t expect these tools to be used much beyond a small niche of home-lab engineers that might stumble across them.&lt;/p>
&lt;h2 id="whats-next">What’s next?&lt;/h2>
&lt;p>As of this writing, I’m working on adding extensible, easy-to-use alerting facilities to these tools. I’m always ready to accept PRs and help from anyone that would like to add their own features.&lt;/p>
&lt;p>Now, I just need to get significantly better with git.&lt;/p>
&lt;p>In any case, check them out at &lt;a href="http://gitlab.com/gwinans/dba-tools" target="_blank" rel="noopener noreferrer">http://gitlab.com/gwinans/dba-tools&lt;/a> or read the Wiki at &lt;a href="https://gitlab.com/gwinans/dba-tools/wikis/home" target="_blank" rel="noopener noreferrer">https://gitlab.com/gwinans/dba-tools/wikis/home&lt;/a>&lt;/p>
&lt;p>–
&lt;em>Photo by &lt;a href="https://unsplash.com/@iurte?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Iker Urteaga&lt;/a> on &lt;a href="https://unsplash.com/search/photos/tools?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&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></content:encoded><author>Geoff Winans</author><category>DBA Tools</category><category>MariaDB</category><category>MySQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2019/08/dba-tools-minimalist-mysql-tooling_hu_c1630e839fb0625.jpg"/><media:content url="https://percona.community/blog/2019/08/dba-tools-minimalist-mysql-tooling_hu_615ce1dd4f0852a4.jpg" medium="image"/></item><item><title>MySQL 5.6/Maria 10.1 : How we got from 30k qps to 101k qps.....</title><link>https://percona.community/blog/2019/08/07/mysql-how-we-got-from-30k-qps-to-101k-qps/</link><guid>https://percona.community/blog/2019/08/07/mysql-how-we-got-from-30k-qps-to-101k-qps/</guid><pubDate>Wed, 07 Aug 2019 07:52:45 UTC</pubDate><description>Late one evening, I was staring at one of our large MySQL installations and noticed the database was hovering around 7-10 run queue length (48 cores, ~500 gigs memory, fusionIO cards). I had been scratching my head on how to get more throughput from the database. This blog records the changes I made to tune performance in order to achieve a 300% better throughput in MySQL. I tested my theories on MySQL 5.6/Maria 10.1. While with 5.7 DBAs would turn to performance_schema for the supporting metrics, I hope that you find the process interesting nevertheless.</description><content:encoded>&lt;p>Late one evening, I was staring at one of our large MySQL installations and noticed the database was hovering around 7-10 run queue length (48 cores, ~500 gigs memory, fusionIO cards). I had been scratching my head on how to get more throughput from the database. This blog records the changes I made to tune performance in order to achieve a 300% better throughput in MySQL. I tested my theories on MySQL 5.6/Maria 10.1. While with 5.7 DBAs would turn to &lt;em>performance_schema&lt;/em> for the supporting metrics, I hope that you find the process interesting nevertheless.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/08/tuning-mysql-for-throughput.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;h2 id="view-from-an-oracle-rdbms-dba">View from an Oracle RDBMS DBA…&lt;/h2>
&lt;p>For context, I came to MySQL from a background as an Oracle RDBMS DBA, and this informs my expectations. For this exercise, unlike with Oracle RDBMS, I had no access to view &lt;em>wait events&lt;/em> so that I could see where my database was struggling. At least, no access in MySQL 5.6/Maria 10.1 without taking a performance hit by using &lt;em>performance_schema&lt;/em>, which was less efficient in these earlier versions.&lt;/p>
&lt;p>In fact, overall, I find that MySQL has far fewer bells and whistles than Oracle at the database level. I constantly whine to my team mates how MySQL provides less knobs compared to Oracle. Even for just creating an index. Without counting, I can confidently say there are over 50 permutations combinations I could use. For example initrans, pctfree, pct, reverse, function, w/o gathering statistics… Admittedly some may be obsolete and discarded in recent versions, but you get my point. :)&lt;/p>
&lt;p>Oracle allows the DBA’s to tune blocks in an index or a table, their physical characteristics….all the way to pinning tables in buffer, tuning specific latches used for buffer cache so one can get rid of cache buffer chains waits with help of a hidden parameter  :)&lt;/p>
&lt;p>Anyway, I digress. Back to the challenges of MySQL!&lt;/p>
&lt;h2 id="tuning-mysql-a-process">Tuning MySQL… a process&lt;/h2>
&lt;p>Given the version of MySQL that provided this challenge, one of the few tools you have access to is the output from &lt;code>show engine innodb status&lt;/code>. While that has a wealth of information, I haven’t yet found a single source of good documentation for each of the metrics shown in the report. I repeatedly saw these &lt;em>waits&lt;/em> in the SEMAPHOREs 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-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">buf0buf.c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">row0rel.cc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">btr0btr.c&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Very naturally I started with reference books available on MySQL’s website, traversing through countless blogs, and sniffing through the code. Only after I had looked at multiple sources did I begin to get a gist of the metrics available in the status report. My research over the next few nights led me to a few different parameters. These ultimately helped me to find the answers I needed.&lt;/p>
&lt;h2 id="making-the-changes-that-mattered">Making the changes that mattered&lt;/h2>
&lt;p>Here is a quick snippet that I changed from the default – or lower – values set by a previous DBA.&lt;/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">innodb_buffer_pool_instances=32
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table_open_cache_instances=12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table_open_cache=8000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table_definition_cache=12000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_change_buffer_size=5&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Some other parameters that I changed are shown next. Although these are very scenario specific, they all helped in tuning one or other of the performance problems I was encountering:&lt;/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">innodb_purge_batch_size=5000 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">optimizer_search_depth=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_file_size=32g
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_buffer_size=1G&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Plus, I set innodb_adaptive_hash_index_parts to 32. &lt;em>Note:&lt;/em> this parameter may be called &lt;code>innodb_adaptive_hash_partitions&lt;/code> in some db versions.&lt;/p>
&lt;p>I will try and explain them to the best of my knowledge and understanding.&lt;/p>
&lt;p>&lt;code>innodb_buffer_pool_instances&lt;/code> had to be increased to allow a greater number of latches to access the buffer pool. Ideally we want to keep this parameter either equal to or a little lower than the number of cores. In this case we set this at half of the number of cores. We have other boxes in the farm with fewer cores and prefer to keep to standard configs and not have snowflakes!&lt;/p>
&lt;p>&lt;code>table_open_cache_instance&lt;/code> also provided similar performance improvement for all queries accessing table metadata. If you are a heavy user of adaptive hash indexes, splitting innodb_adaptive_hash_parts/innodb_adaptive_hash_partitions (depending on your db version) to a higher number of partitions helps a lot with concurrency. It allows you to split hash indexes into  different partitions and to remove contention with hot tables access.&lt;/p>
&lt;p>We reduced &lt;code>innodb_change_buffer_size&lt;/code> to 5% from its default 25% because change buffer was never used more than 400mb. With the default value the change buffer had ~90gb allocated.&lt;/p>
&lt;p>This provided for a lot more data and indices to fit into the buffer pool.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Overall this set of parameters tuning worked for us and for our workload. We saw a great performance benefit from these changes. It was the first time we ever surpassed 100k qps without changing the code or hardware. Please make sure to understand what each parameter does, and to test your workload against them.&lt;/p>
&lt;p>&lt;em>—&lt;/em>&lt;/p>
&lt;p>&lt;em>&lt;a href="https://unsplash.com/search/photos/raspberry?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Photo by &lt;/a>&lt;a href="https://unsplash.com/@joaosilas?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">João Silas&lt;/a>&lt;a href="https://unsplash.com/search/photos/raspberry?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer"> on &lt;/a>&lt;a href="https://unsplash.com/search/photos/mystery?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&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></content:encoded><author>Gurnish Anand</author><category>MariaDB</category><category>MySQL</category><category>performance</category><media:thumbnail url="https://percona.community/blog/2019/08/tuning-mysql-for-throughput_hu_771cfd6c2edbf6b8.jpg"/><media:content url="https://percona.community/blog/2019/08/tuning-mysql-for-throughput_hu_222ac1377e0a2344.jpg" medium="image"/></item><item><title>How to Build a Percona Server "Stack" on a Raspberry Pi 3+</title><link>https://percona.community/blog/2019/08/01/how-to-build-a-percona-server-stack-on-a-raspberry-pi-3/</link><guid>https://percona.community/blog/2019/08/01/how-to-build-a-percona-server-stack-on-a-raspberry-pi-3/</guid><pubDate>Thu, 01 Aug 2019 12:50:36 UTC</pubDate><description>The blog post How to Compile Percona Server for MySQL 5.7 in Raspberry Pi 3 by Walter Garcia, inspired me to create an updated install of Percona Server for the Raspberry Pi 3+.</description><content:encoded>&lt;p>The blog post &lt;a href="https://www.percona.com/blog/2018/08/22/how-to-compile-percona-server-for-mysql-5-7-in-raspberry-pi-3/" target="_blank" rel="noopener noreferrer">&lt;em>How to Compile Percona Server for MySQL 5.7 in Raspberry Pi 3&lt;/em>&lt;/a> by Walter Garcia, inspired me to create an updated install of Percona Server for the &lt;a href="https://www.raspberrypi.org/products/" target="_blank" rel="noopener noreferrer">Raspberry Pi 3+&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/Percona-installation-on-Raspberry-Pi-3.jpg" alt="Percona installation on Raspberry Pi 3+" />&lt;/figure>&lt;/p>
&lt;p>This how-to post covers installing from source and being able to use &lt;a href="https://www.percona.com/software/mysql-database" target="_blank" rel="noopener noreferrer">Percona Server for MySQL&lt;/a> in any of your maker projects. I have included everything you need to have a complete Percona Server, ready to store data collection for your weather station, your GPS data, or any other project you can think of that would require data collection in a database.&lt;/p>
&lt;p>My years of hands-on support of Percona Server enable me to customize the install a bit. I wanted to build a full Percona “Stack” including XtraBackup, and Percona Toolkit.&lt;/p>
&lt;h2 id="hardware-and-software">Hardware and Software&lt;/h2>
&lt;ul>
&lt;li>Tested on a Raspberry PI 3B and 3B+&lt;/li>
&lt;li>OS is Raspbian Buster. You can download it here: &lt;a href="https://www.raspberrypi.org/downloads/raspbian/" target="_blank" rel="noopener noreferrer">https://www.raspberrypi.org/downloads/raspbian/&lt;/a>&lt;/li>
&lt;li>I choose the option: Raspbian Buster with Desktop.&lt;/li>
&lt;li>64GB SD Card, not required, but would not suggest less than 32GB. For best performance use and SD card that is between 90 - 100MB per sec.&lt;/li>
&lt;/ul>
&lt;h2 id="the-step-by-step-guide">The Step-by-Step Guide&lt;/h2>
&lt;p>Let’s get on and build!&lt;/p>
&lt;h3 id="1-prep-your-raspberry-pi">1. Prep Your Raspberry PI&lt;/h3>
&lt;p>You will notice I use sudo rather often, even during the make and cmake. I found that running as the default pi user for the install gave me issues. Using sudo for root based commands is the best practice that I always try to follow.&lt;/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-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get upgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install screen cmake debhelper autotools-dev libaio-dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">automake libtool bison bzr libgcrypt20-dev flex autoconf libtool libncurses5-dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mariadb-client-10.0 libboost-dev libreadline-dev libcurl4-openssl-dev libtirpc-dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create a swapfile. Very much needed for these two compiles.&lt;/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 dd if=/dev/zero of=/swapfile2GB bs=1M count=2048
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkswap /swapfile2GB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo swapon /swapfile2GB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod 0600 /swapfile2GB&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="2-build-percona-server-for-mysql">2. Build Percona Server for MySQL&lt;/h3>
&lt;p>This will take about 3.5 to 4 hours to run. Download percona-server 5.7.26 source tar ball&lt;/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://www.percona.com/downloads/Percona-Server-5.7/Percona-Server-5.7.26-29/source/tarball/percona-server-5.7.26-29.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Extract to /home/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-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-5.7.26-29
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo cmake -DDOWNLOAD_BOOST=ON -DWITH_BOOST=$HOME/boost .
&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;h3 id="3-build-percona-xtrabackup">3. Build Percona XtraBackup&lt;/h3>
&lt;p>This will take about 3 hours.&lt;/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 apt-get install libcurl4-gnutls-dev libev-dev libev4&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Note: installing the package libcurl4-gnutls-dev  will remove the package libcurl4-openssl-dev . I had compile failures for XtraBackup when libcurl4-openssl-dev  was installed. Download XtraBackup 2.4.14&lt;/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">wget https://www.percona.com/downloads/Percona-XtraBackup-2.4/Percona-XtraBackup-2.4.14/source/tarball/percona-xtrabackup-2.4.14.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Extract to /home/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-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">cd percona-xtrabackup-2.4.14
&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 cmake -DWITH_BOOST=$HOME/boost -DBUILD_CONFIG=xtrabackup_release -DWITH_MAN_PAGES=OFF
&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;h3 id="4-build-percona-toolkit">4. Build Percona Toolkit&lt;/h3>
&lt;p>Done in a few minutes.&lt;/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">wget https://www.percona.com/downloads/percona-toolkit/3.0.13/source/tarball/percona-toolkit-3.0.13.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>extract to /home/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-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">cd percona-toolkit-3.0.13
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">perl Makefile.PL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make test
&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;h3 id="5-create-the-mysqsl-user">5. Create the mysqsl user&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">sudo useradd mysql -d /var/lib/mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create directories for mysql to 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-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 mkdir -p /var/lib/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir /var/lib/mysql/binlog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir /var/lib/mysql/tmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir /var/log/mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Change ownership of directories to mysql 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-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">sudo chown -R mysql:mysql /var/lib/mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown mysql:mysql /var/log/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;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="6-prep-mycnf">6. Prep my.cnf&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-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">sudo rm -fR /etc/mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>I like to remove any leftover mysql directories or files in /etc before I create my file in the next 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-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 vi /etc/my.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Add these lines, below, to your new my.cnf 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-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">[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 = /var/lib/mysql/data
&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">skip-external-locking
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">log-bin = /var/lib/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">innodb_data_home_dir = /var/lib/mysql/data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_group_home_dir = /var/lib/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 = 128M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">innodb_log_file_size = 16M
&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 = 1
&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;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Save the my.cnf 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-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">sudo chown mysql:mysql /etc/my.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="7-initialize-the-database-files">7. Initialize the database files&lt;/h3>
&lt;p>At this point, you can initialize the database 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-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">sudo /usr/local/mysql/bin/mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/var/lib/mysql/data&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="8-start-percona-server">8. Start Percona Server&lt;/h3>
&lt;p>This is the exciting part coming up. We are going to start Percona 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">sudo /usr/local/mysql/bin/mysqld_safe --defaults-file=/etc/my.cnf --user=mysql &amp;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If everything went well you should see the following lines in your /var/log/mysql/mysqld.log .```
2019-06-24T19:56:52.071765Z 0 [Note] Server hostname (bind-address): ‘*’; port: 3306
2019-06-24T19:56:52.072251Z 0 [Note] IPv6 is available.
2019-06-24T19:56:52.072385Z 0 [Note]   - ‘::’ resolves to ‘::’;
2019-06-24T19:56:52.072770Z 0 [Note] Server socket created on IP: ‘::’.
2019-06-24T19:56:52.132587Z 0 [Note] InnoDB: Buffer pool(s) load completed at 190624 15:56:52
2019-06-24T19:56:52.136886Z 0 [Note] Failed to start slave threads for channel ’’
2019-06-24T19:56:52.178087Z 0 [Note] Event Scheduler: Loaded 0 events
2019-06-24T19:56:52.179153Z 0 [Note] /usr/local/mysql/bin/mysqld: ready for connections.
Version: ‘5.7.26-29-log’  socket: ‘/var/lib/mysql/mysql.sock’  port: 3306 Source distribution&lt;/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">### 9. Test login to Percona Server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>mysql -u root –socket=/var/lib/mysql/mysql.sock&lt;/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">If you plan on keeping this as an active Percona Server I **strongly advise** you to remove the root user and create your own privileged 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">First, stop Percona Server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>/usr/local/mysql/bin/mysqladmin -u root –socket=/var/lib/mysql/mysql.sock shutdown&lt;/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">Create the mysqld.server and enable it.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>sudo vi /etc/systemd/system/mysqld.service
[Unit]
Description=Percona Server Version 5.7.x
After=syslog.target
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld –defaults-file=/etc/my.cnf
TimeoutSec=300
WorkingDirectory=/usr/local/mysql/bin
#Restart=on-failure
#RestartPreventExitStatus=1
PrivateTmp=true
sudo systemctl enable 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-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">Now if everything was done correctly you should be able to reboot your Pi and Percona Server will auto start on OS Boot.
&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 is it, you now have an entire Percona Server for MySQL up and running, with XtraBackup for your daily backups and Percona Toolkit to assist you with daily and complicated tasks. If you try this out, I'd love to hear about the uses you make of your Percona Server on a Raspberry Pi.
&lt;/span>&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">_Image based on Photo by [Hector Bermudez](https://unsplash.com/@hectorbermudez?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/raspberry?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)_
&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 content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and 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;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div></content:encoded><author>Wayne Leutwyler</author><category>MySQL</category><category>Percona Server for MySQL</category><category>Raspberry Pi</category><category>Toolkit</category><media:thumbnail url="https://percona.community/blog/2019/07/Percona-installation-on-Raspberry-Pi-3_hu_bac5ba8285ff105a.jpg"/><media:content url="https://percona.community/blog/2019/07/Percona-installation-on-Raspberry-Pi-3_hu_c834587c85969757.jpg" medium="image"/></item><item><title>MySQL Optimizer: Naughty Aberrations on Queries Combining WHERE, ORDER BY and LIMIT</title><link>https://percona.community/blog/2019/07/29/mysql-optimizer-naughty-aberrations-on-queries-combining-where-order-by-and-limit/</link><guid>https://percona.community/blog/2019/07/29/mysql-optimizer-naughty-aberrations-on-queries-combining-where-order-by-and-limit/</guid><pubDate>Mon, 29 Jul 2019 11:50:51 UTC</pubDate><description>Sometimes, the MySQL Optimizer chooses a wrong plan, and a query that should execute in less than 0.1 second ends-up running for 12 minutes!This is not a new problem: bugs about this can be traced back to 2014, and a blog post on this subject was published in 2015.But even if this is old news, because this problem recently came yet again to my attention, and because this is still not fixed in MySQL 5.7 and 8.0, this is a subject worth writing about.</description><content:encoded>&lt;p>Sometimes, the MySQL Optimizer chooses a wrong plan, and a query that should execute in less than 0.1 second ends-up running for 12 minutes!This is not a new problem: bugs about this can be traced back to 2014, and a blog post on this subject was published in 2015.But even if this is old news, because this problem recently came yet again to my attention, and because this is still not fixed in MySQL 5.7 and 8.0, this is a subject worth writing about.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/mysql-optimizer-choose-wrong-path.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;h2 id="the-mysql-optimizer">The MySQL Optimizer&lt;/h2>
&lt;p>Before looking at the problematic query, we have to say a few words about the optimizer.The &lt;a href="https://dev.mysql.com/doc/internals/en/optimizer.html" target="_blank" rel="noopener noreferrer">Query Optimizer&lt;/a> is the part of query execution that chooses the query plan.A &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/execution-plan-information.html" target="_blank" rel="noopener noreferrer">Query Execution Plan&lt;/a> is the way MySQL chooses to execute a specific query.It includes index choices, join types, table query order, temporary table usage, sorting type … You can get the execution plan for a specific query using the &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/explain.html" target="_blank" rel="noopener noreferrer">EXPLAIN command&lt;/a>.&lt;/p>
&lt;h2 id="a-case-in-question">A Case in Question&lt;/h2>
&lt;p>Now that we know what are the Query Optimizer and a Query Execution Plan, I can introduce you to the table we are querying. The SHOW CREATE TABLE for our table is 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-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> SHOW CREATE TABLE _test_jfg_201907G
&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">Table: _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Create Table: CREATE TABLE `_test_jfg_201907` (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`str1` varchar(150) DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`id1` int(10) unsigned NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`id2` bigint(20) unsigned DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`str2` varchar(255) DEFAULT 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">[...many more id and str fields...]
&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_datetime` datetime NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">`update_datetime` datetime DEFAULT 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">KEY `key1` (`id1`,`id2`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">) ENGINE=InnoDB AUTO_INCREMENT=_a_big_number_ DEFAULT CHARSET=utf8
&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>And this is not a small table (it is not very big either though…):&lt;/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"># ls -lh _test_jfg_201907.ibd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r----- 1 mysql mysql 11G Jul 23 13:21 _test_jfg_201907.ibd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now we are ready for the problematic query (I ran PAGER cat > /dev/null before to skip printing the 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-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> SELECT * FROM _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE id1 = @v AND id2 IS NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY id DESC LIMIT 20;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">20 rows in set (27.22 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Hum, this query takes a long time (27.22 sec) considering that the table has an index on id1 and id2. Let’s check the query execution 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-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> EXPLAIN SELECT * FROM _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE id1 = @v AND id2 IS NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY id DESC LIMIT 20G
&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">id: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">select_type: SIMPLE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table: _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">partitions: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">possible_keys: key1
&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">key_len: 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ref: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rows: 13000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">filtered: 0.15
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Extra: Using where
&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>What ? The query is not using the index key1, but is scanning the whole table (key: PRIMARY in above EXPLAIN) ! How can this be ? The short explanation is that the optimizer thinks — or should I say hopes — that scanning the whole table (which is already sorted by the id field) will find the limited rows quick enough, and that this will avoid a sort operation. So by trying to avoid a sort, the optimizer ends-up losing time scanning the table.&lt;/p>
&lt;h2 id="some-solutions">Some Solutions&lt;/h2>
&lt;p>How can we solve this ? The first solution is to hint MySQL to use key1 as shown below. Now the query is almost instant, but this is not my favourite solution because if we drop the index, or if we change its name, the query will fail.&lt;/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 * FROM _test_jfg_201907 USE INDEX (key1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE id1 = @v AND id2 IS NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY id DESC LIMIT 20;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">20 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A more elegant, but still very hack-ish, solution is to prevent the optimizer from using an index for the ORDER BY. This can be achieved with the modified ORDER BY clause below (thanks to &lt;a href="http://code.openark.org/blog/" target="_blank" rel="noopener noreferrer">Shlomi Noach&lt;/a> for suggesting this solution on a MySQL Community Chat). This is the solution I prefer so far, even if it is still somewhat a hack.&lt;/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> SELECT * FROM _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE id1 = @v AND id2 IS NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY (id+0) DESC LIMIT 20;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">20 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>A third solution is to use the &lt;a href="https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/" target="_blank" rel="noopener noreferrer">Late Row Lookups&lt;/a> trick. Even if the post about this trick is 10 years old, it is still useful — thanks to my colleague Michal Skrzypecki for bringing it to my attention. This trick basically forces the optimizer to choose the good plan because the query is modified with the intention of making the plan explicit. This is an elegant hack, but as it makes the query more complicated to understand, I prefer not to use 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-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 y.* FROM (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT id FROM _test_jfg_201907
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WHERE id1 = @v AND id2 IS NOT NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER BY id DESC LIMIT 20) x
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">JOIN _test_jfg_201907 y ON x.id = y.id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ORDER by y.id DESC;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">20 rows in set (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="the-ideal-solution">The ideal solution…&lt;/h2>
&lt;p>Well, the best solution would be to fix the bugs below. I claim Bug#74602 is not fixed even if it is marked as such in the bug system, but I will not make too much noise about this as Bug#78612 also raises attention on this problem:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://bugs.mysql.com/bug.php?id=74602" target="_blank" rel="noopener noreferrer">Bug#74602: Optimizer prefers wrong index because of low_limit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bugs.mysql.com/bug.php?id=78612" target="_blank" rel="noopener noreferrer">Bug#78612: Optimizer chooses wrong index for ORDER BY&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PS-1653" target="_blank" rel="noopener noreferrer">PS-1653: Optimizer chooses wrong index for ORDER BY DESC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://perconadev.atlassian.net/browse/PS-4935" target="_blank" rel="noopener noreferrer">PS-4935: Optimizer choosing full table scan (instead of index range scan) on query order by primary key with limit.&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>PS-4935 is a duplicate of PS-1653 that I opened a few months ago. In that report, I mention a query that is taking 12 minutes because of a bad choice by the optimizer (when using the good plan, the query is taking less than 0.1 second).&lt;/p>
&lt;p>One last thing before ending this post: I wrote above that I would give a longer explanation about the reason for this bad choice by the optimizer. Well, this longer explanation has already been written by Domas Mituzas in 2015, so I am referring you to his &lt;a href="https://dom.as/2015/07/30/on-order-by-optimization/" target="_blank" rel="noopener noreferrer">on ORDER BY optimization&lt;/a> post for more details.&lt;/p>
&lt;p>&lt;em>–&lt;/em>&lt;/p>
&lt;p>&lt;em>Photo by &lt;a href="https://unsplash.com/@jamie452?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Jamie Street&lt;/a> on &lt;a href="https://unsplash.com/search/photos/wrong?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&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. The content is not edited or tested by Percona, and 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;div class="comments">
&lt;h2 id="6-comments">6 Comments&lt;/h2>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="http://oysteing.blogspot.com/">Øystein Grøvlen&lt;/a>
&lt;span>July 29, 2019 at 9:43 am&lt;/span>
&lt;/div>
Hi JF,
&lt;p>I think this behavior may be expected if there is a correlation between the columns. For example, that id2 is more likely to be NULL for high (recent?) values of id. The MySQL optimizer does not have any statistics on how columns are correlated. Hence, it is not be able to effectively determine how many rows it needs to read to find the first 20 rows that satisfies the WHERE clause.&lt;/p>
&lt;p>Bug#74602 describes a scenario where column values were not correlated. This particular problem was fixed in 5.7.
Bug#78612 seems to be caused by the use of a prefix index, which does not seem to be relevant here.&lt;/p>
&lt;p>However, there are probably other bug reports that describes the problem you are facing. In order to address this problem, I think MySQL needs to add statistics on correlation between columns.&lt;/p>
&lt;p>Unfortunately, as Domas decribes, the optimizer trace does not contain any information on the cost calculations made when it decides to switch to an index that provides sorting. Hence, it is not straight-forward to verify why this choice was made.&lt;/p>
&lt;/div>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="https://jfg-mysql.blogspot.com/">Jean-François Gagné&lt;/a>
&lt;span>July 29, 2019 at 9:59 am&lt;/span>
&lt;/div>
Hi Øystein, thanks for the details about Bug#74602 and Bug#78612.
&lt;blockquote>
&lt;p>In order to address this problem, I think MySQL needs to add statistics on correlation between columns.&lt;/p>&lt;/blockquote>
&lt;p>This might be a solution, but I am sure there are others. Tracking correlations might be very complicated. A more simple solution might be to identify plans that are “probabilistic” (like the worse case I show in this post) and to not let queries using those plans run for too long before trying an alternative plan. Also, in the case of plans that might have a very worse case (like the one in this post), maybe running both queries in parallel and killing the other when one completes might be another way to avoid this problem.&lt;/p>
&lt;/div>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="http://oysteing.blogspot.com/">Øystein Grøvlen&lt;/a>
&lt;span>July 30, 2019 at 5:13 am&lt;/span>
&lt;/div>
Hi,
&lt;p>I think it is an interesting idea to let the optimizer have a fallback plan, in case its original estimates is off. The challenge is how to detect in time that the estimates are off. Maybe it would be easier to just switch to the more safe plan if the execution takes longer than the estimate for the safe plan. (Unfortunately, it is not straight-forward to translate query cost to execution time in MySQL.) Another aspect is diagnostics. It must be a way for the user to determine which plan was actually used.&lt;/p>
&lt;p>Maybe, the optimizer could be a bit more cautious, and choose a safe plan over a more risky, but potentially quicker plan. In your case, there will be a pretty accurate estimate for the number of rows that need to be read when using the secondary index, while how many rows needs to be read using the primary index depends on how the interesting rows are distributed.&lt;/p>
&lt;/div>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="">Jeremy&lt;/a>
&lt;span>July 31, 2019 at 4:08 pm&lt;/span>
&lt;/div>
While in an ideal world I would like to see this fixed my solution is turn towards the application. It is easier to grow application servers than database servers. After all with solutions like Nginx and such one can easily have a farm of whatever application servers (PHP, Python, Java) and just keep adding more.
&lt;p>I generally keep queries super simple and let the application server(s) do the heavy lifting. For example sort. I almost never ask the database server(s) to sort in my own applications. That is wasting DB cycles on something the application layer can do quite easily and faster. So I am like just dump the raw data DB to app.&lt;/p>
&lt;p>Thus keeping in mind: “The fastest query is the query you do NOT run”. I prefer to dump as much heavy lifting onto the application and let the database layer handle as little as possible. As stated I would rather spin up another app server than a DB server.&lt;/p>
&lt;p>Of course I understand in some limited cases this isn’t always possible. Still in the vast majority of deployments there is an application layer. Also one could turn toward solutions like ProxySQL to cache bad queries although that doesn’t address the bug.&lt;/p>
&lt;p>Finally, as stated, I would like to see this bug fixed. However I still wouldn’t ask the DB to sort in most cases.&lt;/p>
&lt;/div>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="">s&lt;/a>
&lt;span>August 3, 2019 at 4:27 am&lt;/span>
&lt;/div>
also see &lt;a href="https://bugs.mysql.com/bug.php?id=95543">https://bugs.mysql.com/bug.php?id=95543&lt;/a> (optimizer prefers index for order by rather than filtering – (70x slower))
&lt;/div>
&lt;div class="comment">
&lt;div class="info">
&lt;a href="https://jfg-mysql.blogspot.com/">Jean-François Gagné&lt;/a>
&lt;span>July 28, 2021 at 5:48 pm&lt;/span>
&lt;/div>
Another blog post on the same subject (with a patch that was merged in 5.7 and 8.0):
&lt;a href="https://blog.jcole.us/2019/09/30/reconsidering-access-paths-for-index-ordering-a-dangerous-optimization-and-a-fix/">https://blog.jcole.us/2019/09/30/reconsidering-access-paths-for-index-ordering-a-dangerous-optimization-and-a-fix/&lt;/a>
&lt;/div>
&lt;/div></content:encoded><author>Jean-François Gagné</author><category>bugs</category><category>MySQL</category><category>optimizer</category><category>performance</category><media:thumbnail url="https://percona.community/blog/2019/07/mysql-optimizer-choose-wrong-path_hu_e5b755342fc0fd9a.jpg"/><media:content url="https://percona.community/blog/2019/07/mysql-optimizer-choose-wrong-path_hu_75a34687f5d0d9cd.jpg" medium="image"/></item><item><title>Impact of innodb_file_per_table Option On Crash Recovery Time</title><link>https://percona.community/blog/2019/07/23/impact-of-innodb_file_per_table-option-on-crash-recovery-time/</link><guid>https://percona.community/blog/2019/07/23/impact-of-innodb_file_per_table-option-on-crash-recovery-time/</guid><pubDate>Tue, 23 Jul 2019 13:34:14 UTC</pubDate><description>Starting at version MySQL5.6+ by default innodb_file_per_table is enabled and all data is stored in separate tablespaces. It provides some advantages.</description><content:encoded>&lt;p>Starting at version MySQL5.6+ by default innodb_file_per_table is enabled and all data is stored in separate tablespaces. It provides some &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html" target="_blank" rel="noopener noreferrer">advantages&lt;/a>.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/logo-mysql-170x115.png" alt="MySQL Logo" />&lt;/figure>&lt;/p>
&lt;p>I will highlight some of them:&lt;/p>
&lt;ul>
&lt;li>You can reclaim disk space when truncating or dropping a table stored in a file-per-table tablespace. Truncating or dropping tables stored in the shared &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_system_tablespace" title="system tablespace" target="_blank" rel="noopener noreferrer">system tablespace&lt;/a> creates free space internally in the system tablespace data files (&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_ibdata_file" title="ibdata file" target="_blank" rel="noopener noreferrer">ibdata files&lt;/a>) which can only be used for new InnoDB data.&lt;/li>
&lt;li>You can store specific tables on separate storage devices, for I/O optimization, space management, or backup purposes.&lt;/li>
&lt;li>You can monitor table size at a file system level without accessing MySQL.&lt;/li>
&lt;li>Backups taken with &lt;a href="https://www.percona.com/software/mysql-database/percona-xtrabackup" target="_blank" rel="noopener noreferrer">Percona XtraBackup&lt;/a> takes less space (compared with the physical backup of ibdata files)&lt;/li>
&lt;/ul>
&lt;h3 id="problem">Problem&lt;/h3>
&lt;p>There are disadvantages &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html" target="_blank" rel="noopener noreferrer">described&lt;/a> on MySQL man page but I found another one that is not mentioned: if you have a huge number of tables, the crash recovery process may take a lot of time. During crash recovery the MySQL daemon scans .ibd 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">2019-07-16 21:00:04 6766 [Note] InnoDB: Starting crash recovery.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-07-16 21:00:04 6766 [Note] InnoDB: Reading tablespace information from the .ibd files...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Started at Jul 16 23:46:52:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Version: '5.6.39-83.1-log' socket: ......&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>During startup time I checked MySQL behavior and found that MySQL opens files one by one. In my test case it was 1400000+ tables and it took 02:46:48 just to scan ibd files.&lt;/p>
&lt;p>To prevent such a long downtime we decided to move all the tables to shared tablespaces.&lt;/p>
&lt;h3 id="solution--moving-tables-to-shared-tablespaces">Solution – moving tables to shared tablespaces&lt;/h3>
&lt;ol>
&lt;li>Make sure that you have enough space on disk.&lt;/li>
&lt;li>Modify my.cnf and add the files.&lt;/li>
&lt;li>Restart MySQL and wait until it creates the data files.&lt;/li>
&lt;li>Move your InnoDB tables to shared tablespaces.&lt;/li>
&lt;/ol>
&lt;p>You can use this script:&lt;/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"># Get table list that stored in own tablespace (SPACE>0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql -NB information_schema -e "select NAME from INNODB_SYS_TABLES WHERE name not like 'SYS_%' AND name not like 'mysql/%' AND SPACE > 0" | split -l 30000 - tables;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Generate SQL script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">for file in `ls tables*`;
&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">perl -e '$curdb=""; while(&lt;STDIN>) {chomp; ($db,$table) = split(///); if ($curdb ne $db ) { print "USE $db;n"; $curdb=$db; } print "ALTER TABLE $table engine=innodb;n"; }' &lt; $file > $file.SQL;
&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"># Apply files $file.SQL ( you can use parallel execution ) :
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat&lt;&lt;EOF>convert.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">file=$1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql &lt; ${file}
&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"># Do not forget to fix my.cnf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql -e "set global innodb_file_per_table = 0"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x convert.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># run 10 parallel threads
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ls tables*.SQL | xargs -n1 -P10 ./convert.sh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>What the script does:&lt;/p>
&lt;ol>
&lt;li>Retrieves all tables that are occupying their own tablespace&lt;/li>
&lt;li>Generates SQL code in this pattern USE DB_X; ALTER TABLE TBL_Y engine=innodb;&lt;/li>
&lt;li>Applies the SQL scripts in parallel.&lt;/li>
&lt;/ol>
&lt;p>After changing file_per_table to 0 and moving the InnoDB 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">2019-07-17 22:16:47 976 [Note] InnoDB: Reading tablespace information from the .ibd files...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2019-07-17 22:25:45 976 [Note] mysqld: ready for connections.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>Using the default value of innodb_file_per_table (ON) is not always a good choice. In my test case: 4000+ databases, 1400000+ tables. I reduced recovery time from 02:46:48 to 00:08:58 seconds. That’s 18 times less! Remember, there is no “golden my.cnf config”, and each case is special. Optimize MySQL configuration according to your needs.&lt;/p>
&lt;p>&lt;em>–&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. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource, please &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p>
&lt;p>&lt;em>Cartoon source &lt;a href="https://imgur.com/" target="_blank" rel="noopener noreferrer">https://imgur.com/&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Timur Solodovnikov</author><category>MySQL</category><category>MySQL</category><category>MySQL Crash Recovery</category><category>Open Source Databases</category><media:thumbnail url="https://percona.community/blog/2019/07/logo-mysql-170x115_hu_be9204a60de7f14a.jpg"/><media:content url="https://percona.community/blog/2019/07/logo-mysql-170x115_hu_1f5d5e082bdcb177.jpg" medium="image"/></item><item><title>The Concept of Materialized Views in MongoDB Sharded Clusters</title><link>https://percona.community/blog/2019/07/16/concept-materialized-views-mongodb-sharded-clusters/</link><guid>https://percona.community/blog/2019/07/16/concept-materialized-views-mongodb-sharded-clusters/</guid><pubDate>Tue, 16 Jul 2019 10:14:41 UTC</pubDate><description>In one of my past blogs I explained the contribution of MongoDB® views in organization security. In this blog, I will take it one step further and I will try to approach the concept of a materialized view in MongoDB. In computing, a materialized view is a database object that contains the results of a query (definition taken from Wikipedia). If you are already familiar with MongoDB views (or you read my blog), you are now probably wondering why I am calling the MongoDB views materialized while it’s well known that they are computed on the fly? Well, the answer is that in this blog, I am not going to discuss the built-in view capabilities of MongoDB – which by the way are not materialized –but for a technique on how to build, maintain and use a materialized views in a MongoDB sharded cluster.</description><content:encoded>&lt;p>In one of my past &lt;a href="https://www.objectrocket.com/blog/mongodb/enhance-your-organization-security-with-mongodb-views/" target="_blank" rel="noopener noreferrer">blogs&lt;/a> I explained the contribution of MongoDB® views in organization security. In this blog, I will take it one step further and I will try to approach the concept of a materialized view in MongoDB. In computing, a materialized view is a database object that contains the results of a query (definition taken from Wikipedia). If you are already familiar with MongoDB views (or you read my &lt;a href="https://www.objectrocket.com/blog/mongodb/enhance-your-organization-security-with-mongodb-views/" target="_blank" rel="noopener noreferrer">blog&lt;/a>), you are now probably wondering why I am calling the MongoDB views materialized while it’s well known that they are computed on the fly? Well, the answer is that in this blog, I am not going to discuss the built-in view capabilities of MongoDB – which by the way are not materialized –but for a technique on how to build, maintain and use a materialized views in a MongoDB sharded cluster.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/MongoDB-materialized-views.jpg" alt="MongoDB materialized views" />&lt;/figure>&lt;/p>
&lt;h2 id="mongodb-materialized-views-use-case">MongoDB materialized views use case&lt;/h2>
&lt;p>Before I begin with the implementation details, I will analyze a use case that is a perfect fit for a materialized view. The use case will help you also understand why the concept of materialized views can be really useful on a sharded cluster. I am not stating that materialized views aren’t useful on replica-sets, it’s just sometimes a covering index can substitute a materialized view. In a sharded cluster, a sharded collection receives two types of queries: targeted and scatter-gather.&lt;/p>
&lt;p>Targeted queries use the shard key on the query predicates. Most of the time these queries have to access only one shard before returning results to the driver, as the shard key routes the request properly. Scatter-gather queries are the exact opposite, they have to access each and every shard to return results for a query. Scatter-gather is not ideal. At a small scale, they are not considered as a major issue, and as a matter of fact, some queries maybe be faster with scatter-gather (divide and conquer) but what happens if half of the application queries are scatter-gather? This is not an uncommon scenario, as a collection may have two popular query patterns that their query predicates don’t overlap. For example, a collection with document structure {&lt;code>_id&lt;/code>, &lt;code>_a&lt;/code>, &lt;code>_b&lt;/code>, &lt;code>_c&lt;/code>}, and the application queries 50% on &lt;code>_id&lt;/code> and 50% on &lt;code>_a&lt;/code>. If you choose {&lt;code>_id:1&lt;/code> or &lt;code>_id:&lt;/code> hashed} as the shard key, queries on a will be scattered gather and vice-versa.&lt;/p>
&lt;h2 id="how-materialized-views-might-help-and-some-challenges">How materialized views might help… and some challenges&lt;/h2>
&lt;p>Materialized views can help us overcome the “evil” scatter-gather queries. For the above scenario, we will create a satellite collection with {&lt;code>_id&lt;/code>, &lt;code>_a&lt;/code>} where we are going to copy both fields from our main collection &lt;code>{_id, _a, _b, _c}&lt;/code>. The only difference is that the satellite collection will be sharded on &lt;code>{_a:1}&lt;/code> or &lt;code>{_a:hashed}&lt;/code>. A query on &lt;code>_a&lt;/code>, will first hit the satellite collection, fetch the associated &lt;code>_id&lt;/code> and use it to query the main collection.&lt;/p>
&lt;p>At this point, you will be wondering why two queries are better than one. Well, you have to think of what is happening within the database layer during a query on &lt;code>_a&lt;/code>. If you have N shards the mongos have to send the read request on all N shards. Some shards may return zero results but the database would have already wasted resources to execute the query. If you perform two queries, your database will execute exactly two queries, and only two shards will be busy with the “read transaction”.&lt;/p>
&lt;p>However, I must be frank, I am describing the perfect world and the ideal use case. What if the query on &lt;code>_a&lt;/code> returns more than one result? It’s a challenge, then, to identify where one query against the main collection is better than a read transaction on both satellite-main collections. If &lt;code>_a&lt;/code> tends to be unique our approach should be better but in cases where it returns a high number of documents – more than half of the number of shards (N/2) – maybe it is not a good solution.&lt;/p>
&lt;p>Another challenge is the way we are going to populate the satellite collection. We can treat it, like a cache, query for an &lt;code>_a&lt;/code> and if you can’t find it on the satellite collection then add it from the main one. If &lt;code>_a&lt;/code> is immutable we can construct the satellite collection in the background, dump/restore or using custom code. During the “initial sync”, we must not forget to track for changes, either using change streams or oplog or custom code in the application tier. If &lt;code>_a&lt;/code> is immutable you will need an upsert operation in the satellite collection for any new inserts operation in the main collection and replay any remove operations that happen in main. That’s also the way to keep both collections in sync after you finish with the “initial sync”. If &lt;code>_a&lt;/code> is not immutable, you have to propagate the related updates as well. That’s more tricky, it actually means you have to modify the shard key value. In MongoDB 4.2 it is allowed (thanks to distributed transactions), but all version prior to 4.2 it will give you an exception if you attempt it. An update on &lt;code>_a&lt;/code> on the main collection should be replayed as an insert followed by a delete on the satellite collection.&lt;/p>
&lt;p>The challenges list doesn’t stop here. We have to consider the case, that &lt;code>_a&lt;/code> may not a good shard key (may create hotspots, poor cardinality). Also, consider the extra storage and extra writes that will happen to your cluster. A materialized view is not good approach to every use case but is very handy when the fields are not changing very often (ideally at all) and they have decent cardinality (ideally unique).&lt;/p>
&lt;h2 id="mongodb-42">MongoDB 4.2&lt;/h2>
&lt;p>As I was writing this post, MongoDB 4.2 went from development to release candidate. MongoDB 4.2 is a game-changer when it comes to materialized views, as it offers built-in support for it. The $merge  operator (from the aggregation framework) can be used to create a materialized view. The database will take the responsibility to build and maintain the view – I see some smiles already – but it comes with a few restrictions too. In the future, and when 4.2 goes GA, I will expand this post to include the $merge operator.&lt;/p>
&lt;p>–&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource, please &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p>
&lt;p>&lt;em>Photo by &lt;a href="https://unsplash.com/@erol?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Erol Ahmed&lt;/a> on &lt;a href="https://unsplash.com/search/photos/leaves?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Antonios Giannopoulos</author><category>MongoDB</category><media:thumbnail url="https://percona.community/blog/2019/07/MongoDB-materialized-views_hu_c361287fd75561b6.jpg"/><media:content url="https://percona.community/blog/2019/07/MongoDB-materialized-views_hu_68c9c7998c3f9e31.jpg" medium="image"/></item><item><title>Tame Kubernetes with These Open-Source Tools</title><link>https://percona.community/blog/2019/07/08/tame-kubernetes-with-open-source-tools/</link><guid>https://percona.community/blog/2019/07/08/tame-kubernetes-with-open-source-tools/</guid><pubDate>Mon, 08 Jul 2019 13:03:15 UTC</pubDate><description>Kubernetes’ popularity as the most-preferred open-source container-orchestration system has skyrocketed in the recent past. The overall container market is expected to cross USD 2.7 billion by 2020 with a CAGR of 40 percent. Three orchestrators spearhead this upward trend, namely Kubernetes, Mesos, and Docker Swarm. However, referring to the graph below, Kubernetes clearly leads the pack.</description><content:encoded>&lt;p>&lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a>’ popularity as the most-preferred open-source container-orchestration system has skyrocketed in the recent past. The &lt;a href="https://enterprisersproject.com/article/2017/11/kubernetes-numbers-10-compelling-stats" target="_blank" rel="noopener noreferrer">overall container market&lt;/a> is expected to cross USD 2.7 billion by 2020 with a CAGR of 40 percent. Three orchestrators spearhead this upward trend, namely Kubernetes, &lt;a href="http://mesos.apache.org/" target="_blank" rel="noopener noreferrer">Mesos&lt;/a>, and &lt;a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener noreferrer">Docker Swarm&lt;/a>. However, referring to the graph below, Kubernetes clearly leads the pack.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/kubernetes-growth.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;p>&lt;em>source: &lt;a href="https://medium.com/@rdodev/saved-you-an-analyst-read-on-kubernetes-growth-2018-edition-810367876981" target="_blank" rel="noopener noreferrer">https://medium.com/@rdodev/saved-you-an-analyst-read-on-kubernetes-growth-2018-edition-810367876981 &lt;/a>&lt;/em>&lt;/p>
&lt;p>The automation and infrastructural capabilities of Kubernetes are transforming the DevOps space,  thereby enhancing the value of the business through software. With Kubernetes you can deploy, scale, and &lt;a href="https://www.percona.com/live/19/sites/default/files/digital_rack_aws.pdf" target="_blank" rel="noopener noreferrer">manage cloud-native databases&lt;/a> and applications from anywhere. No wonder, data scientists and &lt;a href="https://www.manipalprolearn.com/data-science/post-graduate-certificate-program-in-data-science-and-machine-learning-manipal-academy-higher-education" target="_blank" rel="noopener noreferrer">machine learning engineers&lt;/a> love Kubernetes and apply it to improve their productivity. As Kubernetes continues to evolve and grow in complexity, we need to be ready with solutions that simplify Kubernetes, thereby enhancing your development work. Here is a comprehensive list of Kubernetes tools that can help you tame this orchestrator, many of them open source. I have divided them into five functional categories.&lt;/p>
&lt;h2 id="1-tools-for-automating-cluster-deployments">1. Tools for Automating Cluster Deployments&lt;/h2>
&lt;p>Automated Kubernetes cluster services are a hot topic today because they eliminate much of the deployment and management hassles. An ideal application should consume declarative manifests, bootstrap fully-functioning clusters, and ensure that the K8 clusters are highly available.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/kubernetes-sigs/kubespray" target="_blank" rel="noopener noreferrer">&lt;strong>KubeSpray&lt;/strong>&lt;/a> is a great choice for individuals who know Ansible. You can deploy this Ansible-driven cluster deployment tool on AWS, GCE, Azure, OpenStack, Baremetal, and Oracle Cloud Infrastructure.&lt;/li>
&lt;li>&lt;a href="https://conjure-up.io/" target="_blank" rel="noopener noreferrer">&lt;strong>Conjure-Up&lt;/strong>&lt;/a> can deploy the Canonical distribution of Kubernetes across several cloud providers using simple commands. The tool has native AWS integration, yet supports other cloud providers like GCE, Azure, Joyent, and OpenStack.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://github.com/kubernetes/kops" target="_blank" rel="noopener noreferrer">Kops&lt;/a>&lt;/strong> or &lt;strong>Kubernetes Operations&lt;/strong> can automate the provisioning of K8 clusters in Amazon Web Services (officially supported) and GCE (beta support). The tool allows you to take full control of the cluster lifecycle, from infrastructure provisioning to cluster deletion.&lt;/li>
&lt;li>&lt;a href="https://github.com/kubernetes-incubator/kube-aws" target="_blank" rel="noopener noreferrer">&lt;strong>Kube-AWS&lt;/strong>&lt;/a> is a command-line tool that creates/updates/destroys fully-functional clusters using Amazon Web Services, namely CloudFormation, Auto Scaling, Spot Fleet, and KMS among others.&lt;/li>
&lt;li>You might also like to check out the &lt;a href="https://www.percona.com/software/percona-kubernetes-operators" target="_blank" rel="noopener noreferrer">&lt;strong>Percona Kubernetes operators&lt;/strong>&lt;/a> for Percona XtraDB Cluster and Percona Server for MongoDB.&lt;/li>
&lt;/ul>
&lt;h2 id="2-cluster-monitoring-tools">2. Cluster Monitoring Tools&lt;/h2>
&lt;p>Monitoring Kubernetes clusters is critical in a microservice architecture. The following graph shows the top cluster monitoring tools available today.
&lt;figure>&lt;img src="https://percona.community/blog/2019/07/tools-services-to-monitor-kubernetes-clusters.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Source: &lt;a href="https://thenewstack.io/5-tools-monitoring-kubernetes-scale-production/" target="_blank" rel="noopener noreferrer">https://thenewstack.io/5-tools-monitoring-kubernetes-scale-production/&lt;/a>&lt;/em>&lt;/p>
&lt;p>Here are our recommendations.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://prometheus.io/" target="_blank" rel="noopener noreferrer">&lt;strong>Prometheus&lt;/strong>&lt;/a> is an open-source Cloud Native Computing Foundation (CNCF) tool that offers enhanced querying, visualization, and alerting features.&lt;/li>
&lt;li>&lt;a href="https://github.com/google/cadvisor" target="_blank" rel="noopener noreferrer">&lt;strong>CAdvisor&lt;/strong>&lt;/a> or &lt;strong>Container Advisor&lt;/strong> comes embedded into the kubelet, the primary node agent that runs on each node in the cluster. The tool focuses on container-level performance and provides an understanding of the resource usage and performance characteristics of the running containers.&lt;/li>
&lt;li>&lt;a href="https://www.datadoghq.com/" target="_blank" rel="noopener noreferrer">&lt;strong>Datadog&lt;/strong>&lt;/a> is a good monitoring tool for those who prefer working with a fully-managed SaaS solution. It has a simple user interface to monitor containers. Further, it hosts metrics, such as the CPU and RAM. Its open source projects can be accessed in &lt;a href="https://github.com/DataDog" target="_blank" rel="noopener noreferrer">github&lt;/a>.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://github.com/kubernetes-retired/heapster" target="_blank" rel="noopener noreferrer">Heapster&lt;/a>&lt;/strong> was a native supporter of Kubernetes and is installed as a pod inside Kubernetes. Thus, it can effectively gather data from the containers and pods inside the cluster. Unfortunately developers have retired the project, but you can still access the open source code.&lt;/li>
&lt;/ul>
&lt;h2 id="3-security-tools">3. Security Tools&lt;/h2>
&lt;p>Since Kubernetes effectively automates the provisioning and configuration of containers and provides IP-based security to each pod in the cluster, it has become the de facto container orchestrator. However, Kubernetes cannot offer advanced security monitoring and compliance enforcement, making it important for you to rely on the below-mentioned tools to secure your container stack and in turn &lt;a href="https://www.manipalprolearn.com/blog/decoding-devops-security-three-best-practices" target="_blank" rel="noopener noreferrer">bolster DevOps security&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/aporeto-inc" target="_blank" rel="noopener noreferrer">&lt;strong>Aporeto&lt;/strong>&lt;/a> offers runtime protection to containers, microservices, and cloud and legacy applications, thereby securing Kubernetes workloads. It provides a cloud-network firewall system to secure apps running in distributed environments.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.twistlock.com/" target="_blank" rel="noopener noreferrer">Twistlock&lt;/a>&lt;/strong> is designed to monitor applications deployed on Kubernetes for vulnerability, compliance issues, whitelisting, firewalling, and offer runtime protection to containers. In fact, it had compliance controls for enforcing HIPAA and PCI regulations on the K8 containers. The latest version adds forensic analysis that can reduce runtime overhead.&lt;/li>
&lt;li>&lt;a href="https://neuvector.com/" target="_blank" rel="noopener noreferrer">&lt;strong>NeuVector&lt;/strong>&lt;/a> was designed to safeguard the entire K8 cluster. The container security product can protect applications at all stages of deployment.&lt;/li>
&lt;li>&lt;a href="https://sysdig.com/products/secure/" target="_blank" rel="noopener noreferrer">&lt;strong>Sysdig Secure&lt;/strong>&lt;/a> offers a set of tools for monitoring container runtime environments. Sysdig designed this tool for deep integrations with container orchestration tools and to run along with other tools, such as Sysdig Monitor.&lt;/li>
&lt;/ul>
&lt;h2 id="4-development-tools">4. Development Tools&lt;/h2>
&lt;p>Kubernetes applications consist of multiple services, each running in its own container. Developing and debugging them on a remote Kubernetes cluster can be a cumbersome undertaking. Here are a few development tools that can ease the process of developing and debugging the services locally.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://www.telepresence.io/" target="_blank" rel="noopener noreferrer">Telepresence&lt;/a>&lt;/strong> is a development tool that allows you to use custom tools, namely debugger and IDE to simplify the developing and &lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/local-debugging/" target="_blank" rel="noopener noreferrer">local debugging process&lt;/a>. It provides full access to ConfigMap and other services running on the remote cluster.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://keel.sh/" target="_blank" rel="noopener noreferrer">Keel&lt;/a>&lt;/strong> automates Kubernetes deployment updates as soon as the new version is available in the repository. It is stateless and robust and deploys Kubernetes services through labels, annotations, and charts.&lt;/li>
&lt;li>&lt;a href="https://github.com/kubernetes/helm" target="_blank" rel="noopener noreferrer">&lt;strong>Helm&lt;/strong>&lt;/a> is an application package manager for Kubernetes that allows the description of the application structure using helm-charts and simple commands.&lt;/li>
&lt;li>&lt;a href="https://github.com/logzio/apollo/wiki/Getting-Started-with-Apollo" target="_blank" rel="noopener noreferrer">&lt;strong>Apollo&lt;/strong>&lt;/a> is an open-source application that helps operators create and deploy their services to Kubernetes. It also allows the user to view logs and revert deployments at any time.&lt;/li>
&lt;/ul>
&lt;h2 id="5-kubernetes-based-serverless-frameworks">5. Kubernetes-Based Serverless Frameworks&lt;/h2>
&lt;p>Due to Kubernetes’ ability to orchestrate containers across clusters of hosts, serverless FaaS frameworks rely on Kubernetes for orchestration and management. Here are a few Kubernetes-based serverless frameworks that can help build a serverless environment.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://kubeless.io/" target="_blank" rel="noopener noreferrer">Kubeless&lt;/a>&lt;/strong> is a Kubernetes-native open-source serverless framework that allows developers to deploy bits of code without worrying about the underlying infrastructure. It uses Kubernetes resources to offer auto-scaling, API routing, monitoring, and troubleshooting&lt;/li>
&lt;li>&lt;a href="https://platform9.com/fission/" target="_blank" rel="noopener noreferrer">&lt;strong>Fission&lt;/strong>&lt;/a> is an open-source serverless framework released by Platform9, a software company that manages hybrid cloud infrastructure with Kubernetes cloud solutions. The framework helps developers manage their applications without bothering about the plumbing related to containers.&lt;/li>
&lt;li>&lt;a href="https://github.com/knative" target="_blank" rel="noopener noreferrer">&lt;strong>KNative&lt;/strong>&lt;/a> is a platform used by operators to build serverless solutions on top of Kubernetes. It isn’t an outright serverless solution. KNative acts as a layer between Kubernetes and the serverless framework, enabling developers to run the application anywhere that Kubernetes runs.&lt;/li>
&lt;/ul>
&lt;h2 id="time-for-action">Time for Action&lt;/h2>
&lt;p>Open-source container-orchestration systems like Kubernetes have helped users overcome several challenges in the DevOps space. However, as Kubernetes continues to evolve, your development, monitoring, and security strategies need to change. Use the Kubernetes tools and frameworks shared in this post to simplify cluster orchestration and deployment, making it easy to deploy this popular orchestrator.&lt;/p>
&lt;p>–&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource, please &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p>
&lt;p>&lt;em>Featured image photograph &lt;a href="https://pixabay.com/photos/boat-wheel-ship-sea-nautical-2387790/" target="_blank" rel="noopener noreferrer">AnnaD on Pixabay&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Gaurav Belani</author><category>DevOps</category><category>Kubernetes</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2019/07/kubernetes-management-tools_hu_130ec8379081eda6.jpg"/><media:content url="https://percona.community/blog/2019/07/kubernetes-management-tools_hu_e815360ef9f99cd8.jpg" medium="image"/></item><item><title>Percona Live Presents: Globalizing Player Accounts with MySQL at Riot Games</title><link>https://percona.community/blog/2019/05/28/percona-live-presents-globalizing-player-accounts-mysql-riot-games/</link><guid>https://percona.community/blog/2019/05/28/percona-live-presents-globalizing-player-accounts-mysql-riot-games/</guid><pubDate>Tue, 28 May 2019 16:48:15 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/05/riot-games.jpg" alt=" " />&lt;/figure>&lt;/p>
&lt;p>During my presentation at &lt;a href="https://www.percona.com/live/19/sessions/globalizing-player-accounts-with-mysql-at-riot-games" target="_blank" rel="noopener noreferrer">Percona Live 2019&lt;/a>, I’ll be talking about how &lt;a href="https://www.riotgames.com/en" target="_blank" rel="noopener noreferrer">Riot Games&lt;/a>, the company behind League of Legends, migrated hundreds of millions of player accounts to unlock opportunities for us to delight players. This meant moving ten geographically distributed databases into a single global database replicated into four AWS regions. I’ll talk about some of the technical decisions we made, the expected vs actual outcomes, and lessons we learned along the way.&lt;/p>
&lt;p>Migrating hundreds of millions of player records without impacting a player’s ability to manage their account and log in was a daunting task. I’ll shed some light on how we managed to handle this data migration while modifying the database schema. I’ll also go into detail on the backend architecture of our accounts service, such as how we use Continuent Tungsten, which we’re leveraging to manage our globally replicated database.&lt;/p>
&lt;p>I gave a &lt;a href="https://www.youtube.com/watch?v=MJpZZm62ZKw" target="_blank" rel="noopener noreferrer">similar version of this talk&lt;/a> at AWS re:Invent last year, and wrote the article “&lt;a href="https://technology.riotgames.com/news/globalizing-player-accounts" target="_blank" rel="noopener noreferrer">Globalizing Player Accounts&lt;/a>” on the &lt;a href="http://technology.riotgames.com" target="_blank" rel="noopener noreferrer">Riot Games Tech Blog&lt;/a>—check out these resources for more deep tech details and context on our accounts solution.&lt;/p>
&lt;h2 id="whod-get-the-most-from-this-presentation">Who’d get the most from this presentation?&lt;/h2>
&lt;p>The presentation will be most helpful for folks who want to learn about strategies for deploying globally replicated databases, especially developers and DBA/DBEs who are building global services. I’ll also discuss how we think about deploying applications that will talk to these types of databases.&lt;/p>
&lt;h2 id="whose-presentations-are-you-most-looking-forward-to">Whose presentations are you most looking forward to?&lt;/h2>
&lt;p>In particular, I’m really looking forward to VividCortex’s talk on &lt;a href="https://www.percona.com/live/19/sessions/optimizing-database-performance-and-efficiency" target="_blank" rel="noopener noreferrer">optimizing performance and efficiency&lt;/a> because I’d like to see their perspectives on performance issues. I’m excited to learn more by comparing their solutions to the ones I’ve seen at my own company.&lt;/p>
&lt;p>I’m also looking forward to the Facebook talks (&lt;a href="https://www.percona.com/live/19/sessions/mysql-replication-and-ha-at-facebook-part-1" target="_blank" rel="noopener noreferrer">Part 1&lt;/a> &amp; &lt;a href="https://www.percona.com/live/19/sessions/mysql-replication-and-ha-at-facebook-part-2" target="_blank" rel="noopener noreferrer">Part 2&lt;/a>) on HA MySQL because I’m interested in this problem space and I’m curious about their solutions for managing data at scale.&lt;/p></content:encoded><author>Tyler Turk</author><category>Events</category><category>MySQL</category><category>Open Source Databases</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/05/riot-games_hu_93c2e4143b4a2238.jpg"/><media:content url="https://percona.community/blog/2019/05/riot-games_hu_77470f9fb67cf964.jpg" medium="image"/></item><item><title>Percona Live Presents: Gonymizer, A Tool to Anonymize Sensitive PostgreSQL Data Tables for Use in QA and Testing</title><link>https://percona.community/blog/2019/05/17/percona-live-gonymizer-tool-anonymize-sensitive-postgresql-data-testing/</link><guid>https://percona.community/blog/2019/05/17/percona-live-gonymizer-tool-anonymize-sensitive-postgresql-data-testing/</guid><pubDate>Fri, 17 May 2019 11:11:58 UTC</pubDate><description>SmithRX is a next generation pharmacy benefit platform that is using the latest technology to radically reshape the prescription benefit management industry. To move quickly, we require the ability to iterate and test new versions of our software using production like data without violating Health Information Portability and Accountability Act (HIPAA) regulations.</description><content:encoded>&lt;p>&lt;a href="https://smithrx.com/" target="_blank" rel="noopener noreferrer">SmithRX&lt;/a>&lt;a href="https://smithrx.com/" target="_blank" rel="noopener noreferrer">
&lt;figure>&lt;img src="https://percona.community/blog/2019/05/gonymizer-postgres-data-anonymizer.jpg" alt="gonymizer postgres data anonymizer" />&lt;/figure>&lt;/a> is a next generation pharmacy benefit platform that is using the latest technology to radically reshape the prescription benefit management industry. To move quickly, we require the ability to iterate and test new versions of our software using production like data without violating Health Information Portability and Accountability Act (HIPAA) regulations.&lt;/p>
&lt;p>At Percona Live 2019, we are introducing a project we open sourced to anonymize our sensitive production data for use in rapid QA and testing of our software. The talk will cover:&lt;/p>
&lt;ul>
&lt;li>An introduction to HIPAA and Protected Health Information (PHI)&lt;/li>
&lt;li>Deciding which parts of your data need to be anonymized&lt;/li>
&lt;li>Column mapping and how to represent relations that need to be anonymized&lt;/li>
&lt;li>An introduction to the design of the software and how it works&lt;/li>
&lt;li>Dumping data from a sensitive source&lt;/li>
&lt;li>Processing the sensitive data to create an anonymized data set&lt;/li>
&lt;li>Loading of the anonymized data set to a QA environment&lt;/li>
&lt;li>How SmithRx is using multiple Kubernetes CronJob to reload our Q/A and development environments daily&lt;/li>
&lt;li>Other examples on how Gonymizer can be used in other scheduling systems such as AWS Lambda&lt;/li>
&lt;li>What this means for you and how you can contribute&lt;/li>
&lt;/ul>
&lt;h3 id="whod-get-the-most-from-the-presentation">Who’d get the most from the presentation?&lt;/h3>
&lt;p>This presentation is intended for software engineers that need a quick and easy way to anonymize their data. Intended for middle level database infrastructure (devops), and continuous integration systems. This presentation is also appropriate for Go developers looking to contribute to  an open source project that is database related. Currently Gonymizer only supports PostgreSQL, but the software has been designed to handle multiple RDBMS in the future so anyone with HIPAA, DISA (Defense Information Systems Agency), or PCI () experience in other RDBMS may find this presentation useful for getting you started on porting Gonymizer to your RDBMS.&lt;/p>
&lt;h3 id="whose-presentations-are-you-most-looking-forward-to">Whose presentations are you most looking forward to?&lt;/h3>
&lt;p>At SmithRx we are currently growing our infrastructure size, automation management, and monitoring systems for our PostgreSQL database tier. There are many presentations we look forward to attending, but the following four talks will be a focus for SmithRx:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/ha-postgresql-on-kubernetes-by-demo" target="_blank" rel="noopener noreferrer">HA PostgreSQL on Kubernetes&lt;/a> by Josh Berkus&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/automated-database-monitoring-at-uber-with-m3-and-prometheus" target="_blank" rel="noopener noreferrer">Automated Database Monitoring at Uber With M3 and Prometheus&lt;/a> by Richard Artoul&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/monitoring-postgresql-with-percona-monitoring-and-management-pmm" target="_blank" rel="noopener noreferrer">Monitoring PostgreSQL with Percona Monitoring and Management (PMM)&lt;/a> by Avinash Vallarapu&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/future-of-postgres" target="_blank" rel="noopener noreferrer">Future of Postgres&lt;/a> by Ken Rugg&lt;/li>
&lt;/ul>
&lt;p>__&lt;/p>
&lt;p>Photo by &lt;a href="https://unsplash.com/photos/bhoj9tHlsiY?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Viktor Talashuk&lt;/a> on &lt;a href="https://unsplash.com/search/photos/mannequin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/p></content:encoded><author>Levi Junkert</author><category>Events</category><category>Kubernetes</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2019/05/gonymizer-postgres-data-anonymizer_hu_cae16e3cef5c69e9.jpg"/><media:content url="https://percona.community/blog/2019/05/gonymizer-postgres-data-anonymizer_hu_dc455ea2d68231b2.jpg" medium="image"/></item><item><title>Percona Live Presents: An Open-Source, Cloud Native Database</title><link>https://percona.community/blog/2019/05/14/percona-live-presents-open-source-cloud-native-database/</link><guid>https://percona.community/blog/2019/05/14/percona-live-presents-open-source-cloud-native-database/</guid><pubDate>Tue, 14 May 2019 17:38:16 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/05/cloud-native-database.jpg" alt="an open source cloud native database" />&lt;/figure>&lt;/p>
&lt;p>During our presentation at &lt;a href="https://www.percona.com/live/19/sessions/an-open-source-cloud-native-database-cndb" target="_blank" rel="noopener noreferrer">Percona Live 2019&lt;/a> Intel and its software partners will introduce the audience to the work we’re doing to enable an open-source framework, we call Cloud Native Database. This is a collaborative effort between &lt;a href="https://intel.com/" target="_blank" rel="noopener noreferrer">Intel&lt;/a>, &lt;a href="https://rockset.com/" target="_blank" rel="noopener noreferrer">Rockset&lt;/a>, &lt;a href="https://planetscale.com/" target="_blank" rel="noopener noreferrer">PlanetScale&lt;/a>, &lt;a href="https://mariadb.org/" target="_blank" rel="noopener noreferrer">MariaDB&lt;/a> and &lt;a href="https://www.percona.com/" target="_blank" rel="noopener noreferrer">Percona&lt;/a>.&lt;/p>
&lt;p>Through the presentation the audience will be introduced to a set of principles and architectural elements that define what we mean by Cloud Native Database. We will discuss Rockset’s RocksDB-Cloud library and how it works with Facebook’s MyRocks storage engine. We also will cover PlanetScale’s Vitess project and their use of Kubernetes for deployment of our Database-as-a-Service (DBaaS) mechanisms. Lastly we share data on the performance and scale characteristics of the architecture and components that we have developed.&lt;/p>
&lt;h3 id="whod-get-the-most-from-the-presentation">Who’d get the most from the presentation?&lt;/h3>
&lt;p>Developers, DBAs, database practitioners in general, and folks interested in building/deploying/operating Stateful, Cloud Native Micro-Services on Kubernetes will all benefit from this presentation.&lt;/p>
&lt;h3 id="whose-presentations-are-you-most-looking-forward-to">Whose presentations are you most looking forward to?&lt;/h3>
&lt;p>I’m really looking forward to &lt;a href="https://www.percona.com/live/19/sessions/vitess-running-sharded-mysql-on-kubernetes" target="_blank" rel="noopener noreferrer">Vitess: Running Sharded MySQL on Kubernetes&lt;/a> by Sugu Sougoumarane and Dan Kozlowski. The folks at PlanetScale are doing some amazing stuff with the Vitess project. I’m also super excited to get audience feedback on our second presentation, &lt;a href="https://www.percona.com/live/19/sessions/a-discussion-on-the-advantages-afforded-mysql-dbaas-offerings-hosted-on-intels-next-gen-platform" target="_blank" rel="noopener noreferrer">A Discussion on the Advantages Afforded MySQL DBaaS offerings hosted on Intel’s Next Gen Platform&lt;/a>&lt;strong>.&lt;/strong>&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/04/Percona-Live-2019.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>–&lt;/p>
&lt;p>Photo by &lt;a href="https://unsplash.com/photos/h-rP5KSC2W0?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Michael Weidner&lt;/a> on &lt;a href="https://unsplash.com/search/photos/cloud?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/p></content:encoded><author>Dave Cohen</author><category>david.cohen</category><category>Events</category><category>MySQL</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/05/cloud-native-database_hu_154194f8fc206df5.jpg"/><media:content url="https://percona.community/blog/2019/05/cloud-native-database_hu_6730cf7c29fb35ed.jpg" medium="image"/></item><item><title>Percona Live Presents: The State of Databases in 2019</title><link>https://percona.community/blog/2019/05/09/percona-live-presents-state-databases-2019/</link><guid>https://percona.community/blog/2019/05/09/percona-live-presents-state-databases-2019/</guid><pubDate>Thu, 09 May 2019 10:20:27 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/05/state-of-databases-2019.jpg" alt="state of databases 2019" />&lt;/figure>&lt;/p>
&lt;p>At this year’s Percona Live I am talking about &lt;a href="https://www.percona.com/live/19/sessions/the-state-of-databases-in-2019" target="_blank" rel="noopener noreferrer">The State of Databases in 2019&lt;/a>. As a Software Engineer in the thick of the Database landscape, there are two problems that I see repeatedly.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Due to the massive explosion of database solutions, it has become very difficult to evaluate what database solution will serve the best for one’s use case, and&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The constant tug of war between database operators and users. While users (software developers) want the best suited database solution for their use case, and operators have to find a way to deploy databases for the entire organization’s needs.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>I feel these are both real world problems which will be experienced by any organization using  a database at any scale. In my talk, I will touch upon both areas, and share my experience working with various database engines over my career.&lt;/p>
&lt;h3 id="whod-get-the-most-from-the-presentation">Who’d get the most from the presentation?&lt;/h3>
&lt;p>My talk is oriented primarily towards software developers and database operators. However, it is of general interest for all stakeholders including people in the C-suite. The database landscape is very complex and is very hard to understand so anybody who would like to understand the landscape in 2019 could benefit from my talk.&lt;/p>
&lt;h3 id="what-im-looking-forward-to-the-most">What I’m looking forward to the most…&lt;/h3>
&lt;p>While I am looking forward to a significant number of talks spread over both days of the conference, a few stand out.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/live/19/sessions/mysql-technology-evolutions-at-facebook" target="_blank" rel="noopener noreferrer">MySQL Technology Evolutions at Facebook&lt;/a>&lt;/p>
&lt;p>I am a big fan of how things actually run in production. I believe code &amp; software engineering practices mature when your code runs in production. As many people know, Facebook has a huge MySQL installation. It is exciting to learn how they have productionized MySQL to serve over a billion users.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/live/19/sessions/databases-at-scale-at-square" target="_blank" rel="noopener noreferrer">Databases at Scale, at Square&lt;/a>&lt;/p>
&lt;p>Again, I love to know how different organizations productionize their databases. With Square being bang in the middle of enterprise and retail financial ecosystems, I am very interested in listening to this talk on how they balance the various requirements and deliver a great database product.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/live/19/sessions/storing-time-series-in-2019-modern-database-performance-scalability-and-reliability-comparison" target="_blank" rel="noopener noreferrer">Storing Time Series in 2019: Modern Database Performance, Scalability, and Reliability Comparison&lt;/a>&lt;/p>
&lt;p>I am a committer on the Apache Cassandra project - a database that is frequently used for storing time series data. I also want to hear about the specifics of the monitoring system that they have built leveraging Cassandra. It is always interesting to hear first hand experiences from our users.&lt;/p>
&lt;p>–&lt;/p>
&lt;p>&lt;em>Photo by &lt;a href="https://unsplash.com/photos/Q1p7bh3SHj8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">NASA&lt;/a> on &lt;a href="https://unsplash.com/search/photos/data?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Dave Cohen</author><category>Events</category><category>Open Source Databases</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/05/state-of-databases-2019_hu_22a71a457a8ad3d7.jpg"/><media:content url="https://percona.community/blog/2019/05/state-of-databases-2019_hu_a4b61c990581d86.jpg" medium="image"/></item><item><title>Percona Live Presents: The First Ever TiDB Track</title><link>https://percona.community/blog/2019/05/06/percona-live-presents-first-ever-tidb-track/</link><guid>https://percona.community/blog/2019/05/06/percona-live-presents-first-ever-tidb-track/</guid><pubDate>Mon, 06 May 2019 20:45:41 UTC</pubDate><description>The PingCAP team has always been a strong supporter of Percona and the wider open source database community. As the people who work day in and day out on TiDB, an open source NewSQL database with MySQL compatibility, open source database is what gets us in the morning, and there’s no better place to share that passion than Percona Live.</description><content:encoded>&lt;p>The PingCAP team has always been a strong supporter of Percona and the wider open source database community. As the people who work day in and day out on &lt;a href="https://github.com/pingcap/tidb" target="_blank" rel="noopener noreferrer">TiDB&lt;/a>, an open source NewSQL database with MySQL compatibility, open source database is what gets us in the morning, and there’s no better place to share that passion than Percona Live.&lt;/p>
&lt;p>At this year’s &lt;a href="https://www.percona.com/live/19/" target="_blank" rel="noopener noreferrer">Percona Live Open Source Database Conference&lt;/a> in Austin, Texas, we are particularly excited to bring you a full track of talks and demo on the latest development in TiDB during Day 1 of the conference.&lt;/p>
&lt;h2 id="who-would-benefit-from-the-tidb-track">Who would benefit from the TiDB track&lt;/h2>
&lt;p>The TiDB track is designed to share with developers, DBAs, and practitioners in general technical know-hows, reproducible benchmarks (no benchmark-eting), and best practices on how TiDB can solve their problems. There are 7 talks total by folks from PingCAP and Intel that cover the full gamut of how you can test, migrate, and use TiDB in the cloud to solve technical problems and deliver business value. Here’s a run down of the talk topics:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/tidb-30-whats-new-and-whats-next" target="_blank" rel="noopener noreferrer">How to benchmark TiDB 3.0, the newest version&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/using-chaos-engineering-to-build-a-reliable-tidb" target="_blank" rel="noopener noreferrer">Using chaos engineering to ensure system reliability&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/leveraging-optane-to-tackle-your-io-challenges-with-tidb" target="_blank" rel="noopener noreferrer">Leveraging Intel Optane to tackle IO challenges&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/deep-dive-into-tidb-sql-layer" target="_blank" rel="noopener noreferrer">A deep look at TiDB’s SQL processing layer, optimized for a distributed system&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/making-htap-real-with-tiflash-a-tidb-native-columnar-extension" target="_blank" rel="noopener noreferrer">Introducing a new columnar storage engine (TiFlash) that makes hybrid OLTP/OLAP a reality&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/making-an-aas-out-of-tidb-building-dbaas-on-a-kubernetes-operator" target="_blank" rel="noopener noreferrer">Building TiDB as a managed service (aka DBaaS) on a Kubernetes Operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/19/sessions/from-mysql-to-tidb-and-back-again" target="_blank" rel="noopener noreferrer">Migration best practices in and out of TiDB from MySQL and MariaDB&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Phew! That’s a lot. I hope you are excited to join us for this track. As Peter Zaitsev and Morgan Tocker (one of the TiDB track speakers) noted in a recent &lt;a href="https://www.percona.com/resources/webinars/how-horizontally-scale-mysql-tidb-while-avoiding-sharding-issues" target="_blank" rel="noopener noreferrer">Percona webinar&lt;/a>, there’s a lot TiDB can do to help scale MySQL while avoiding common manual sharding issues. This track will peel the onion to show you all the fun stuff under the hood.&lt;/p>
&lt;h2 id="whose-presentations-do-you-look-forward-to">Whose presentations do you look forward to?&lt;/h2>
&lt;p>Besides the TiDB track, there are many other presentations we are excited about. In particular, I look forward to attending Stacy Yuan and Yashada Jadhav of PayPal’s talk on &lt;a href="https://www.percona.com/live/19/sessions/mysql-security-and-standardization-at-paypal" target="_blank" rel="noopener noreferrer">MySQL Security and Standardization&lt;/a>, and Vinicius Grippa of Percona’s presentation on &lt;a href="https://www.percona.com/live/19/sessions/enhancing-mysql-security" target="_blank" rel="noopener noreferrer">enhancing MySQL Security&lt;/a>.&lt;/p>
&lt;p>See you soon in Austin!&lt;/p></content:encoded><author>PingCAP</author><category>Kubernetes</category><category>MySQL</category><category>Open Source Databases</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/05/state-of-databases-2019_hu_22a71a457a8ad3d7.jpg"/><media:content url="https://percona.community/blog/2019/05/state-of-databases-2019_hu_a4b61c990581d86.jpg" medium="image"/></item><item><title>Percona Live Presents: The MySQL Query Optimizer Explained Through Optimizer Trace</title><link>https://percona.community/blog/2019/04/24/mysql-query-optimizer-explained-optimizer-trace/</link><guid>https://percona.community/blog/2019/04/24/mysql-query-optimizer-explained-optimizer-trace/</guid><pubDate>Wed, 24 Apr 2019 16:16:09 UTC</pubDate><description>During my presentation at Percona Live 2019 I will show how using Optimizer Trace can give insight into the inner workings of the MySQL Query Optimizer. Through the presentation, the audience will both be introduced to optimizer trace, learn more about the decisions the query optimizer makes, and learn about the query execution strategies the query optimizer has at its disposal. I’ll be covering the main phases of the MySQL optimizer and its optimization strategies, including query transformations, data access strategies, the range optimizer, the join optimizer, and subquery optimization.</description><content:encoded>&lt;p>During my presentation at &lt;a href="https://www.percona.com/live/19/sessions/the-mysql-query-optimizer-explained-through-optimizer-trace" target="_blank" rel="noopener noreferrer">Percona Live 2019&lt;/a> I will show how using Optimizer Trace can give insight into the inner workings of the MySQL Query Optimizer. Through the presentation, the audience will both be introduced to optimizer trace, learn more about the decisions the query optimizer makes, and learn about the query execution strategies the query optimizer has at its disposal. I’ll be covering the main phases of the MySQL optimizer and its optimization strategies, including query transformations, data access strategies, the range optimizer, the join optimizer, and subquery optimization.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/04/oysteing3.jpg" alt="Øystein Grøvlen" />&lt;/figure>&lt;/p>
&lt;h2 id="whod-benefit-most-from-the-presentation">Who’d benefit most from the presentation?&lt;/h2>
&lt;p>DBAs, developers, support engineers and other people who are concerned about MySQL query performance will benefit from this presentation. Knowing the optimizer trace will enable them to understand why the query optimizer selected a particular query plan. This will be very helpful in order to understand how tune their queries for better performance.&lt;/p>
&lt;h2 id="whose-presentations-are-you-most-looking-forward-to">Whose presentations are you most looking forward to?&lt;/h2>
&lt;p>I’m definitely looking forward to &lt;a href="https://www.percona.com/live/19/sessions/a-proactive-approach-to-monitoring-slow-queries" target="_blank" rel="noopener noreferrer">A Proactive Approach to Monitoring Slow Queries&lt;/a> by Shashank Sahni of &lt;a href="https://www.thousandeyes.com/" target="_blank" rel="noopener noreferrer">ThousandEyes Inc&lt;/a>. It is always interesting to learn how users of MySQL monitor their systems to detect and improve slow queries.&lt;/p></content:encoded><author>Øystein Grøvlen</author><category>Events</category><category>MySQL</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/04/oysteing3_hu_6fbb8ac78c811860.jpg"/><media:content url="https://percona.community/blog/2019/04/oysteing3_hu_f3e25d50fc98dc57.jpg" medium="image"/></item><item><title>Percona Live Presents: Vitess – Running Sharded MySQL on Kubernetes</title><link>https://percona.community/blog/2019/04/18/percona-live-presents-vitess-running-sharded-mysql-kubernetes/</link><guid>https://percona.community/blog/2019/04/18/percona-live-presents-vitess-running-sharded-mysql-kubernetes/</guid><pubDate>Thu, 18 Apr 2019 17:19:49 UTC</pubDate><description>The topic I’m presenting addresses a growing and unfulfilled need: the ability to run stateful workloads in Kubernetes. Running stateless applications is now considered a solved problem. However, it’s currently not practical to put databases like MySQL in containers, give them to Kubernetes, and expect it to manage their life cycles.</description><content:encoded>&lt;p>The topic I’m presenting addresses a growing and unfulfilled need: the ability to run stateful workloads in Kubernetes. Running stateless applications is now considered a solved problem. However, it’s currently not practical to put databases like MySQL in containers, give them to Kubernetes, and expect it to manage their life cycles.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/04/sugu_sougoumarane.jpg" alt="Sugu Sougoumarane" />&lt;/figure>
Sugu Sougoumarane, CTO of Planetscale and creator of &lt;a href="https://vitess.io/" target="_blank" rel="noopener noreferrer">Vitess&lt;/a>.&lt;/p>
&lt;p>Vitess addresses this need by providing all the necessary orchestration and safety, and it has multiple years of mileage to show for it. Storage is the last piece of the puzzle that needs to be solved in Kubernetes, and it’s exciting to see people look towards Vitess to fill this gap.&lt;/p>
&lt;h2 id="whod-benefit-most-from-the-presentation">Who’d benefit most from the presentation?&lt;/h2>
&lt;p>Anybody that’s looking to move to Kubernetes and is wondering about what to do about their data is the perfect audience. Needless to say, vitess also addresses problems of scalability. So, those who are looking to scale mysql will also benefit from our talk.&lt;/p>
&lt;h2 id="whose-presentations-are-you-most-looking-forward-to">Whose presentations are you most looking forward to?&lt;/h2>
&lt;p>I’m looking forward to &lt;em>&lt;a href="https://www.percona.com/live/19/sessions/an-open-source-cloud-native-database-cndb" target="_blank" rel="noopener noreferrer">An Open-Source, Cloud Native Database (CNDB)&lt;/a>&lt;/em> by David Cohen, of Intel, and others. They are doing something unique by bridging the gap from legacy systems and cloud-based architectures that are coming up today, and using all open source technology.&lt;/p>
&lt;p>I’ll be presenting my talk &lt;em>&lt;a href="https://www.percona.com/live/19/sessions/vitess-running-sharded-mysql-on-kubernetes" target="_blank" rel="noopener noreferrer">Vitess: Running Sharded MySQL on Kubernetes&lt;/a>&lt;/em> at Percona Live 2019 on Wednesday, May 29 alongside Dan Kozlowski, also of &lt;a href="https://planetscale.com/" target="_blank" rel="noopener noreferrer">PlanetScale&lt;/a>. If you’d like to &lt;a href="https://www.percona.com/live/19/register" target="_blank" rel="noopener noreferrer">register for the conference&lt;/a>, use the code SEEMESPEAK for a 20% discount on your ticket.&lt;/p>
&lt;p>Percona Live 2019 takes place in Austin Texas from May 28 – May 30, &lt;a href="https://www.percona.com/live/19/" target="_blank" rel="noopener noreferrer">view the full programme here&lt;/a>.&lt;/p></content:encoded><author>Sugu Sougoumarane</author><category>Events</category><category>MySQL</category><category>Percona Live 2019</category><media:thumbnail url="https://percona.community/blog/2019/04/sugu_sougoumarane_hu_20c445e194a57b97.jpg"/><media:content url="https://percona.community/blog/2019/04/sugu_sougoumarane_hu_2634aeccf4aaf5bc.jpg" medium="image"/></item><item><title>London Open Source Database Community Meetup</title><link>https://percona.community/blog/2019/03/15/london-open-source-database-community-meetup/</link><guid>https://percona.community/blog/2019/03/15/london-open-source-database-community-meetup/</guid><pubDate>Fri, 15 Mar 2019 09:24:30 UTC</pubDate><description>I strongly believe in the community.</description><content:encoded>&lt;p>I strongly believe in the community.&lt;/p>
&lt;p>Communities are the real strength of open source. Not just the theoretical ability to study, modify and share code – but the fact that other people out there are doing these things. Creating a base of knowledge and a network of relations.These can become work relationships, valuable discussions, open source tools, or even friendships.&lt;/p>
&lt;p>&lt;a href="https://www.meetup.com/London-Open-Source-Database-Meetup/events/259662862/" target="_blank" rel="noopener noreferrer">
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2019/03/london-meetup_hu_18b35a1a448c4c27.jpg 480w, https://percona.community/blog/2019/03/london-meetup_hu_a58d2673e15cc612.jpg 768w, https://percona.community/blog/2019/03/london-meetup_hu_1a968764ca50d9bc.jpg 1400w"
src="https://percona.community/blog/2019/03/london-meetup.jpg" alt="London Open Source Database Meetup" />&lt;/figure>&lt;/a>&lt;/p>
&lt;p>That is why, when I heard that several people from the Percona support team will soon be in London, I badly wanted to organise an event.&lt;/p>
&lt;p>Actually, there was an interesting coincidence. When I asked &lt;a href="https://www.percona.com/blog/author/sveta-smirnova/" target="_blank" rel="noopener noreferrer">Sveta Smirnova&lt;/a> if anyone from Percona lives in London, I already knew I wanted to organise an event with this new meetup group I’ve started: &lt;a href="https://www.meetup.com/London-Open-Source-Database-Meetup/" target="_blank" rel="noopener noreferrer">London Open Source Database meetup&lt;/a>. But when Sveta told me that a whole team of Perconians would soon come to London? Well, trying to organise something big was natural! I asked them to speak about a broad range of technologies. And they came up with some brilliant talk descriptions.&lt;/p>
&lt;p>This is the list of talks (the order may change a bit):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MongoDB ReplicaSet and Sharding&lt;/strong> – Vinodh Krishnaswamy, Support Engineer&lt;/li>
&lt;li>&lt;strong>MySQL 8.0 architecture and Enhancements&lt;/strong> – Lalit Choudhary, Bug Reproduction Analyst&lt;/li>
&lt;li>&lt;strong>Optimizer Histograms: When they Help and When Do Not?&lt;/strong> – Sveta Smirnova, Principal Bug Escalation Specialist&lt;/li>
&lt;li>&lt;strong>New and Maturing Built-in Features in PostgreSQL to Help Build Simple Shards&lt;/strong> – Jobin Augustine, Senior Support Engineer&lt;/li>
&lt;li>&lt;strong>Brothers in Arms: Using ProxySQL + PXC to Ensure Transparent High Availability for your Application&lt;/strong> – Vinicius Grippa, Support Engineer&lt;/li>
&lt;/ul>
&lt;h2 id="details">Details&lt;/h2>
&lt;p>If you will be in or near London on &lt;strong>Wednesday March 27&lt;/strong>, between 7pm and 10pm, please sign up on the &lt;a href="https://www.meetup.com/London-Open-Source-Database-Meetup/events/259662862/" target="_blank" rel="noopener noreferrer">event page&lt;/a> as soon as possible, meet the Percona experts, enjoy a few snacks courtesy of Percona, and be a part of this new idea. The event is being held at Innovation Warehouse in the Farringdon area – it’s above Smithfield Market.&lt;/p>
&lt;p>And I’d like to thank the Percona team for helping me get this new project off the ground. See you there!&lt;/p></content:encoded><author>Federico Razzoli</author><category>Events</category><category>MariaDB</category><category>MongoDB</category><category>MySQL</category><category>PostgreSQL</category><media:thumbnail url="https://percona.community/blog/2019/03/london-meetup_hu_b745b423e8cba352.jpg"/><media:content url="https://percona.community/blog/2019/03/london-meetup_hu_bd036228c3dce9a7.jpg" medium="image"/></item><item><title>#ilovefs Valentine's Day Celebration (I Love Free Software)</title><link>https://percona.community/blog/2019/02/14/ilovefs-valentines-day/</link><guid>https://percona.community/blog/2019/02/14/ilovefs-valentines-day/</guid><pubDate>Thu, 14 Feb 2019 10:21:04 UTC</pubDate><description>Free Software Foundation Europe (FSFE) is celebrating the creators of free software with their I Love Free Software campaign #ilovefs, a social campaign for Valentine’s Day.</description><content:encoded>&lt;p>&lt;a href="https://fsfe.org/" target="_blank" rel="noopener noreferrer">&lt;strong>Free Software Foundation Europe&lt;/strong>&lt;/a> (FSFE) is celebrating the creators of free software with their I Love Free Software campaign #ilovefs, a social campaign for Valentine’s Day.&lt;/p>
&lt;p>The idea is to show some appreciation to the makers of free software. Most of our communications with free software creators are about bugs and feature requests and maybe we just forget to say “Thanks”. So FSFE are trying to provide some balance.&lt;/p>
&lt;p>FSFE were promoting the campaign at FOSDEM at the beginning of this month. Unfortunately my swag parcel arrived a little late to get our widely distributed remote colleagues the balloons and material to pose with, so you just get me!&lt;/p>
&lt;p>Since getting the swag distributed to my colleagues in time for Valentine’s day was a challenge, I headed to my local University in Aberystwyth, Wales to share the goodies, and encourage final year computer science students to celebrate free software.&lt;/p>
&lt;h4 id="share-the-love">Share the love…&lt;/h4>
&lt;p>If you’d like to share your appreciation for free software too:&lt;/p>
&lt;ul>
&lt;li>Read more about the campaign at &lt;a href="https://fsfe.org/campaigns/ilovefs/index.en.html" target="_blank" rel="noopener noreferrer">https://fsfe.org/campaigns/ilovefs/index.en.html&lt;/a>&lt;/li>
&lt;li>Share on social using the hashtag #ilovefs - banners and images can be downloaded from here &lt;a href="https://fsfe.org/campaigns/ilovefs/artwork/artwork.en.html" target="_blank" rel="noopener noreferrer">https://fsfe.org/campaigns/ilovefs/artwork/artwork.en.html&lt;/a>&lt;/li>
&lt;li>Put the date in your diary for next year to remember to show some love for the work of free software creators across the globe&lt;/li>
&lt;/ul>
&lt;p>And of course, twice a year at &lt;a href="https://www.percona.com/live/19/" target="_blank" rel="noopener noreferrer">Percona Live Open Source Database Conferences&lt;/a> EVERYONE loves free software… come and share the love! &lt;a href="https://www.percona.com/live/19/" target="_blank" rel="noopener noreferrer">
&lt;figure>&lt;img src="https://percona.community/blog/2019/02/percona_ilovefs.jpg" alt=" " />&lt;/figure>&lt;/a>
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2019/02/ilovefs-1_hu_9bf1b3d83bc3026b.jpg 480w, https://percona.community/blog/2019/02/ilovefs-1_hu_78609dfc409ac234.jpg 768w, https://percona.community/blog/2019/02/ilovefs-1_hu_e1d6c0f3df1092a7.jpg 1400w"
src="https://percona.community/blog/2019/02/ilovefs-1.jpg" alt=" " />&lt;/figure>
&lt;figure>&lt;img src="https://percona.community/blog/2019/02/ilovefs-5.jpg" alt=" " />&lt;/figure>&lt;/p></content:encoded><author>Lorraine Pocklington</author><category>lorraine.pocklington</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2019/02/ilovefs_postcard_hu_40cb9fe7921eecb7.jpg"/><media:content url="https://percona.community/blog/2019/02/ilovefs_postcard_hu_68b3beaa6144164e.jpg" medium="image"/></item><item><title>An Introduction To MongoDB Replication</title><link>https://percona.community/blog/2019/01/14/introduction-mongodb-replication/</link><guid>https://percona.community/blog/2019/01/14/introduction-mongodb-replication/</guid><pubDate>Mon, 14 Jan 2019 11:21:27 UTC</pubDate><description>MongoDB® is database software that stores data in the same format as JSON. The data structure of the database can be changed when required. The performance of the database is good and developers can easily use to it to connect their code to the database.</description><content:encoded>&lt;p>MongoDB® is database software that stores data in the same format as &lt;a href="https://www.json.org/" target="_blank" rel="noopener noreferrer">JSON&lt;/a>. The data structure of the database can be changed when required. The performance of the database is good and developers can easily use to it to connect their code to the database.&lt;/p>
&lt;p>The database, &lt;a href="https://youtu.be/_ErXhxZV4uQ" target="_blank" rel="noopener noreferrer">MongoDB&lt;/a> is platform independent and runs in the same way on all platforms. It is an open source database and is based on a document-oriented database model. Various forms of data whether text, images, or videos can be stored in the database.&lt;/p>
&lt;h2 id="mongodb-and-why-its-so-popular">MongoDB and Why It’s So Popular?&lt;/h2>
&lt;p>Every record in the database is a document and has field and value pairs. The fields act as a column while the values depend on the data types. The primary key can be assigned to a field, which can connect other tables.&lt;/p>
&lt;p>The Mongo shell can be used to write queries. After the installation of the software, mongo shell can be connected to the MongoDB.&lt;/p>
&lt;p>&lt;a href="https://hub.packtpub.com/mongodb-popular-nosql-database-today/" target="_blank" rel="noopener noreferrer">Mongodb is the most popular NoSQL database&lt;/a>, which helps the users to retrieve data easily. Users do not have to master SQL and users can use simple queries for writing and reading data. MongoDB is also the preferred choice for those who use JavaScript MEAN stack.&lt;/p>
&lt;p>As we become familiar with the database and how it works and get to know its popularity and use, database administrators and tech professionals might choose any institution to acquire a &lt;a href="https://www.simplilearn.com/big-data-and-analytics/mongodb-certification-training" target="_blank" rel="noopener noreferrer">MongoDB Certification&lt;/a> which benefits you by widening your knowledge about the database and stand you out of the crowd.&lt;/p>
&lt;h2 id="replication-in-mongodb">Replication in MongoDB&lt;/h2>
&lt;p>The group of mongod processes creates replicas of a database in order to maintain the same set of data as in the original database. The replica of a database helps in easy deployments.&lt;/p>
&lt;p>Now take a rundown where I have explained in detail about the MongoDB replication.&lt;/p>
&lt;h2 id="redundancy-and-data-availability">#Redundancy and Data Availability&lt;/h2>
&lt;p>Replication of a database helps in easy availability of data. The multiple copies of a database in the form of replicas avoids data loss as if a server crashes, data can be recovered from other servers for the same database.&lt;/p>
&lt;p>Replication also provides easy data read as multiple users can send requests on the same database and get the data quickly. The distribution of data in such a way helps to easily create and deploy distributed applications.&lt;/p>
&lt;h2 id="replica">#Replica&lt;/h2>
&lt;p>The replica set of a database is a group of mongod instances. Each replica set has many data bearing nodes along with optional arbiter nodes. There is only one node that can be used as a primary node while the rest of the nodes are secondary. The power of write operations is with the primary node only.&lt;/p>
&lt;p>The changes done are maintained in an oplog through which all the secondary nodes are replicated. There are some circumstances in which another node can be used as a primary node but it happens very rarely.&lt;/p>
&lt;p>In this diagram, write and read operations are done on the primary node and the secondary nodes are being updated.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/01/intro-mongodb-replication-1.png" alt="intro mongodb replication from MongoDB manuals" />&lt;/figure> &lt;em>Images from MongoDB manual on replication &lt;a href="https://docs.mongodb.com/manual/core/replica-set-primary/" target="_blank" rel="noopener noreferrer">https://docs.mongodb.com/manual/core/replica-set-primary/&lt;/a>&lt;/em>  &lt;/p>
&lt;p>If the primary node is unavailable due to some reason, an eligible secondary node acts as a primary node to provide the data.&lt;/p>
&lt;h2 id="arbiter">#Arbiter&lt;/h2>
&lt;p>Arbiter can also be added as a mongod instance. Though an arbiter does not perform the maintenance of dataset, it maintains a quorum and responds to other secondary nodes by responding to the heartbeat and nomination request by other replica sets. In this diagram, the arbiter is shown.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/01/intro-mongodb-replication-2.png" alt="intro-mongodb-replication-2" />&lt;/figure>&lt;/p>
&lt;h2 id="asynchronous-replication">#Asynchronous Replication&lt;/h2>
&lt;p>Secondary nodes update themselves from the primary node asynchronously. This is useful because the failure of one or more nodes does not affect the functioning of the database and users can easily retrieve data.&lt;/p>
&lt;h2 id="automatic-failover">#Automatic Failover&lt;/h2>
&lt;p>There are situations when the primary node is unable to communicate with other nodes. In this case, a secondary node makes itself as a primary node and the database functions normally. The secondary nominates itself as primary only if the communication electiontimeoutMiliis period extends beyond ten seconds.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/01/intro-mongodb-replication-3.png" alt="intro-mongodb-replication-3" />&lt;/figure>&lt;/p>
&lt;p>The write queries do not process until a secondary node has been nominated as a primary node but read queries work normally. The nomination of a secondary node as the primary node should not exceed 12 seconds as per the replica configuration settings. This time can be modified by using settings.electionTimeoutMillis.&lt;/p>
&lt;p>The replica configuration settings include the following settings in order to set secondary as primary.&lt;/p>
&lt;h5 id="settingschainingallowed">settings.chainingAllowed&lt;/h5>
&lt;p>It has three options&lt;/p>
&lt;ul>
&lt;li>Optional&lt;/li>
&lt;li>Type: boolean&lt;/li>
&lt;li>Default: true&lt;/li>
&lt;/ul>
&lt;p>The secondary nodes can replicate from other members if the default is set to true. If it is false then the secondary node can update itself only from the primary only.&lt;/p>
&lt;h5 id="settingsgetlasterrormodes">settings.getLastErrorModes&lt;/h5>
&lt;ul>
&lt;li>Optional&lt;/li>
&lt;li>Type: document&lt;/li>
&lt;/ul>
&lt;p>The document in the type variable decides whether the data has been successfully written.&lt;/p>
&lt;h5 id="settingsheartbeattimeoutsecs">settings.heartbeatTimeoutSecs&lt;/h5>
&lt;ul>
&lt;li>Optional&lt;/li>
&lt;li>Type: int&lt;/li>
&lt;li>Default: 10&lt;/li>
&lt;/ul>
&lt;p>This sets the number of seconds for a replica set to wait for a successful heartbeat from each other. If the response time of a member do not succeed, then other members that unresponsive member as inaccessible.&lt;/p>
&lt;h5 id="settingselectiontimeoutmillis">settings.electionTimeoutMillis&lt;/h5>
&lt;ul>
&lt;li>Optional.&lt;/li>
&lt;li>Type: int&lt;/li>
&lt;li>Default: 10000 (10 seconds)&lt;/li>
&lt;/ul>
&lt;p>The default time is set in milliseconds to check whether the primary node is accessible or not.&lt;/p>
&lt;h5 id="settingscatchuptimeoutmillis">settings.catchUpTimeoutMillis&lt;/h5>
&lt;ul>
&lt;li>Optional.&lt;/li>
&lt;li>Type: int&lt;/li>
&lt;li>Default: -1&lt;/li>
&lt;/ul>
&lt;p>The time is set in milliseconds for the new primary node and its sync with other replicas. High time reduces the amount of data to be rolled back by other members but increases failover time.&lt;/p>
&lt;h5 id="settingscatchuptakeoverdelaymillis">settings.catchUpTakeoverDelayMillis&lt;/h5>
&lt;ul>
&lt;li>Optional.&lt;/li>
&lt;li>Type: int&lt;/li>
&lt;li>Default: 30000 (30 seconds)&lt;/li>
&lt;/ul>
&lt;p>If a node determines that it is ahead of the primary node then it declares itself as a primary node.&lt;/p>
&lt;h2 id="read-operations">#Read Operations&lt;/h2>
&lt;p>Users can read from primary node only but if they specify a read preference through secondary nodes, they will be able to retrieve data through that source also.&lt;/p>
&lt;h2 id="final-verdict">Final Verdict&lt;/h2>
&lt;p>It can be said that replicating a MongoDB database is very easy and it helps in building distributed applications. The database replica can be deployed on many servers.&lt;/p>
&lt;p>So if a server fails, data can be retrieved from other servers and this will not damage the functioning of the database. JavaScript Professionals can also use this database very easily as they do not have to master SQL and can get data through simple queries.&lt;/p>
&lt;p>All images are from the &lt;a href="https://docs.mongodb.com/manual/replication/" target="_blank" rel="noopener noreferrer">MongoDB Manual&lt;/a> on replication&lt;/p>
&lt;p>—
&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource, please &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p>
&lt;p>Leaf image Photo by &lt;a href="https://unsplash.com/photos/c0rIh0nFTFU?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Chris Lawton&lt;/a> on &lt;a href="https://unsplash.com/search/photos/leaf?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/p></content:encoded><author>Danish Wadhwa</author><category>poster_danish</category><category>Entry Level</category><category>MongoDB</category><category>Replication</category><media:thumbnail url="https://percona.community/blog/2019/01/mongodb-replication_hu_666189f2fd99483e.jpg"/><media:content url="https://percona.community/blog/2019/01/mongodb-replication_hu_a90b44eca695b816.jpg" medium="image"/></item><item><title>Writing a Killer Conference Proposal</title><link>https://percona.community/blog/2019/01/03/writing-killer-conference-proposal/</link><guid>https://percona.community/blog/2019/01/03/writing-killer-conference-proposal/</guid><pubDate>Thu, 03 Jan 2019 10:40:41 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2019/01/writing-a-killer-conference-proposal.jpg" alt="writing a killer conference proposal" />&lt;/figure>&lt;/p>
&lt;p>If you’re planning to submit a proposal to &lt;a href="https://www.percona.com/live/19/" target="_blank" rel="noopener noreferrer">Percona Live&lt;/a> but suffering a little writer’s block, or at least want to be sure to make a good impression on our track selectors, there’s some great content online that can help. If you’re an old hand, you probably won’t need this, though it’s possible you’ll find some interesting stuff here nevertheless.&lt;/p>
&lt;p>Your job is to make it easy for the selectors to choose your talk. Remember, too, that your proposal will be used to ‘sell’ your presentation on the conference website. So try to make it appealing.&lt;/p>
&lt;h3 id="theres-help-online">There’s help online…&lt;/h3>
&lt;p>Here, I list some articles and presentations that could help you along the way:&lt;/p>
&lt;ul>
&lt;li>This article by Russ Unger breaks the task into &lt;a href="https://alistapart.com/article/conference-proposals-that-dont-suck" target="_blank" rel="noopener noreferrer">steps of a process&lt;/a>… those who pride themselves on their technical aptitude rather than their writing chops might find this structured approach helps.&lt;/li>
&lt;li>Experienced presenter Dave Cheney, open source contributor and project member for the Go programming language, has sat on both sides of the fence (proposing and selecting) and &lt;a href="https://dave.cheney.net/2017/02/12/how-to-write-a-successful-conference-proposal" target="_blank" rel="noopener noreferrer">offers some great advice&lt;/a>. Dave also references &lt;a href="https://medium.com/@fox/how-to-write-a-successful-conference-proposal-4461509d3e32" target="_blank" rel="noopener noreferrer">this excellent article&lt;/a> on Medium by Karolina Szczur&lt;/li>
&lt;li>If you prefer to listen and/or watch, then &lt;a href="https://youtu.be/KAzChb4MYCg?t=247" target="_blank" rel="noopener noreferrer">this workshop presentation&lt;/a> by blogger and MongoDB engineer &lt;a href="https://emptysqua.re/blog/global-diversity-cfp-day-workshop/" target="_blank" rel="noopener noreferrer">Jesse Davis&lt;/a> for PyLadies Global Diversity CFP Day 2018 might hit the spot. Or perhaps you prefer the style of an &lt;a href="https://youtu.be/OAQAXVU1jIo?t=121" target="_blank" rel="noopener noreferrer">earlier talk&lt;/a> referenced by Jesse, presented by &lt;a href="https://www.laceyhenschel.com/" target="_blank" rel="noopener noreferrer">Lacie Williams Henschel&lt;/a>. Lacie is a Python and Django consultant.&lt;/li>
&lt;li>Too late for Percona Live in Austin, but this year’s &lt;a href="https://www.globaldiversitycfpday.com/" target="_blank" rel="noopener noreferrer">Global Diversity CFP Day&lt;/a> on March 2 could appeal. If you are a confident and experienced presenter, how about setting up a workshop to share your skills? You can find details on the website.&lt;/li>
&lt;li>Last but not least, O’Reilly hosts dozens of conferences every year and &lt;a href="https://www.oreilly.com/conferences/sample_proposals.html" target="_blank" rel="noopener noreferrer">provides examples&lt;/a> of what they look for in a good proposal.&lt;/li>
&lt;/ul>
&lt;p>So sharpen your pencil and go for it, you’ve nothing to lose. Don’t forget, the &lt;a href="https://perconacfp.hubb.me/" target="_blank" rel="noopener noreferrer">call for papers closes on Sunday, January 20&lt;/a>, so don’t use this as an opportunity to put things off… we hope to release a few talks before the deadline this year, it could be you…Good luck with your submission!&lt;/p>
&lt;p>If you have any great resources to add, please share them via the comments.&lt;/p>
&lt;p>PS If you need any help, you are welcome &lt;a href="mailto:lorraine.pocklington@percona.com">to drop me a line&lt;/a>.&lt;/p>
&lt;p>–&lt;/p>
&lt;p>&lt;em>Photo by &lt;a href="https://unsplash.com/photos/K3uOmmlQmOo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Angelina Litvin&lt;/a> on &lt;a href="https://unsplash.com/search/photos/writing?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Lorraine Pocklington</author><category>lorraine.pocklington</category><category>Entry Level</category><category>Events</category><media:thumbnail url="https://percona.community/blog/2019/01/writing-a-killer-conference-proposal_hu_fba50b67d37578b8.jpg"/><media:content url="https://percona.community/blog/2019/01/writing-a-killer-conference-proposal_hu_dec15045005f7102.jpg" medium="image"/></item><item><title>Some Notes on MariaDB system-versioned Tables</title><link>https://percona.community/blog/2018/12/14/notes-mariadb-system-versioned-tables/</link><guid>https://percona.community/blog/2018/12/14/notes-mariadb-system-versioned-tables/</guid><pubDate>Fri, 14 Dec 2018 14:53:29 UTC</pubDate><description>As mentioned in a previous post, I gave a talk at Percona Live Europe 2018 about system-versioned tables. This is a new MariaDB 10.3 feature, which consists of preserving old versions of a table rows. Each version has two timestamps that indicate the start (INSERT,UPDATE) of the validity of that version, and its end (DELETE, UPDATE). As a result, the user is able to query these tables as they appear at a point in the past, or how data evolved in a certain time range. An alternative name for this feature is temporal table, and I will use it in the rest of this text.</description><content:encoded>&lt;p>As mentioned in a &lt;a href="https://www.percona.com/community-blog/2018/10/17/percona-live-europe-presents-mariadb-system-versioned-tables/" target="_blank" rel="noopener noreferrer">previous post&lt;/a>, I gave a talk at &lt;a href="https://www.percona.com/live/e18/sessions/mariadb-system-versioned-tables" target="_blank" rel="noopener noreferrer">Percona Live Europe 2018&lt;/a> about system-versioned tables. This is a new MariaDB 10.3 feature, which consists of preserving old versions of a table rows. Each version has two timestamps that indicate the start (INSERT,UPDATE) of the validity of that version, and its end (DELETE, UPDATE). As a result, the user is able to query these tables as they appear at a point in the past, or how data evolved in a certain time range. An alternative name for this feature is &lt;em>temporal table&lt;/em>, and I will use it in the rest of this text.
&lt;figure>&lt;img src="https://percona.community/blog/2018/12/mariadb-system-versioned-tables.jpg" alt="mariadb system-versioned tables" />&lt;/figure>&lt;/p>
&lt;p>In this post, I want to talk a bit about temporal tables best practices. Some of the information that I will provide is not present in &lt;a href="https://mariadb.com/kb/en/library/system-versioned-tables/" target="_blank" rel="noopener noreferrer">the documentation&lt;/a>; while they are based on my experience and tests, there could be errors. My suggestions for good practices are also based on my experience and opinions, and I don’t consider them as universal truths. If you have different opinions, I hope that you will share them in the comments or as a separate blog post.&lt;/p>
&lt;h2 id="create-temporal-columns">Create temporal columns&lt;/h2>
&lt;p>It is possible – but optional – to create the columns that contain the timestamps of rows. Since there is no special term for them, I call them &lt;em>temporal columns&lt;/em>. MariaDB allows us to give them any name we like, so I like to use the names valid_from and valid_to, which seem to be some sort of de facto standard in data warehousing. Whichever names you decide to use, I advise you to use them for all your temporal columns and for nothing else, so that the meaning will be clear.
Temporal columns are &lt;em>generated columns&lt;/em>, meaning that their values are generated by MariaDB and cannot be modified by the user. They are also &lt;em>invisible columns&lt;/em>, which means that they can only be read by mentioning them explicitly. In other words, the following query will not return those columns:&lt;/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 * FROM temporal_table;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Also, that query will only show current versions of the rows. In this way, if we make a table temporal, existing applications and queries will continue to work as before.&lt;/p>
&lt;p>But we can still read old versions and obtain timestamp with a query 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-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 *, valid_from, valid_to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">    FROM temporal_table **FOR SYSTEM_TIME ALL**
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">    WHERE valid_from &lt; NOW() - INTERVAL 1 MONTH;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>If we don’t create these columns, we will not be able to read the timestamps of current and old row versions. We will still be able to read data from a point in time or from a time range by using some special syntax. However, I believe that using the consolidated WHERE syntax is easier and more expressive than using some syntax sugar.&lt;/p>
&lt;h2 id="primary-keys">Primary keys&lt;/h2>
&lt;p>For performance reasons, InnoDB tables should always have a primary key, and normally it shouldn’t be updated. Temporal tables provide another reason to follow this golden rule – even on storage engines that are not organised by primary key, like MyISAM.&lt;/p>
&lt;p>The reason is easy to demonstrate with an 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-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 id, valid_from, valid_to FROM t FOR SYSTEM_TIME ALL WHERE id IN (500, 501);
&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 | valid_from | valid_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">| 500 | 2018-12-09 12:22:45.000001 | 2018-12-09 12:23:03.000001 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 501 | 2018-12-09 12:23:03.000001 | 2038-01-19 03:14:07.999999 |
&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 do these results mean? Maybe row 500 has been deleted and row 501 has been added. Or maybe row 500 has been modified, and its id became 501. The timestamps suggest that the latter hypothesis is more likely, but there is no way to know that for sure.&lt;/p>
&lt;p>That is why, in my opinion, we need to be able to assume that UPDATEs never touch primary key values.&lt;/p>
&lt;h2 id="indexes">Indexes&lt;/h2>
&lt;p>Currently, the documentation says nothing about how temporal columns are indexed. However, my conclusion is that the valid_to column is appended to UNIQUE indexes and the primary key. My opinion is based on the results of some EXPLAIN commands, like 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">EXPLAIN SELECT email, valid_to FROM customer ORDER BY email 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"> id: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> select_type: SIMPLE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> table: customer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">possible_keys: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key: unq_email
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key_len: 59
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ref: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rows: 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Extra: Using where; Using index&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This means that the query only reads from a UNIQUE index, and not from table data – therefore, the index contains the email column. It is also able to use the index for sorting, which confirms that email is the first column (as expected). In this way, UNIQUE indexes don’t prevent the same value from appearing multiple times, but it will always be shown at different points in time.&lt;/p>
&lt;p>It can be a good idea to include valid_to or valid_from in some regular indexes, to optimize queries that use such columns for filtering results.&lt;/p>
&lt;h2 id="transaction-safe-temporal-tables">Transaction-safe temporal tables&lt;/h2>
&lt;p>Temporal columns contain timestamps that indicate when a row was INSERTed, UPDATEd, or DELETEd. So, when autocommit is not enabled, temporal columns don’t match the COMMIT time. For most use cases, this behaviour is desirable or at least acceptable. But there are cases when we want to only see committed data, to avoid data inconsistencies that were never seen by applications.&lt;/p>
&lt;p>To do so, we can create a history-precise temporal table. This only works with InnoDB – not with RocksDB or TokuDB, even if they support transactions. A history-precise temporal table doesn’t contain timestamps; instead, it contains the id’s of transactions that created and deleted each row version. If you know PostgreSQL, you are probably familiar with the xmin and xmax columns – it’s basically the same idea, except that in postgres at some point autovacuum will make old row versions disappear. Because of the similarity, for transaction-precise temporal tables, I like to call the temporal columns xmin and xmax.&lt;/p>
&lt;p>From this short description, the astute reader may already see a couple of problems with this approach:&lt;/p>
&lt;ul>
&lt;li>Temporal tables are based on transaction id’s &lt;strong>or&lt;/strong> on timestamps, not both. There is no way to run a transaction-precise query to extract data that were present one hour ago. But think about it: even if it was possible, it would be at least problematic, because transactions are meant to be concurrent.&lt;/li>
&lt;li>Transaction id’s are written in the binary log, but such information is typically only accessible by DBAs. An analyst (someone who’s typically interested in temporal tables) has no access to transaction id’s.&lt;/li>
&lt;/ul>
&lt;p>A partial workaround would be to query tables with columns like created_at and modified_at. We can run queries 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">SELECT created_at, xmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> FROM some_table
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WHERE created_at >= '2018-05-05 16:00:00'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ORDER BY created_at
&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>This will return the timestamp of the first row created since ‘2018-05-05 16:00:00’, as well as the id of the transaction which inserted it.&lt;/p>
&lt;p>While this approach could give us the information we need with a reasonable extra work, it’s possible that we don’t have such columns, or that rows are not inserted often enough in tables that have them.&lt;/p>
&lt;p>In this case, we can occasionally write in a table the current timestamp and the current transaction id. This should allow us to associate a transaction to the timestamp we are interested in. We cannot write all transaction id’s for performance reasons, so we can use two different approaches:&lt;/p>
&lt;ul>
&lt;li>Write the transaction id and the timestamp periodically, for example each minute. This will not create performance problems. On the other hand, we are arbitrarily deciding the granularity of our “log”. This could be acceptable or not.&lt;/li>
&lt;li>Write this information when certain events happen. For example when a product is purchased, or when a user changes their password. This will give us a very precise way to see the data as they appeared during critical events, but will not allow us to investigate with the same precision other types of events.&lt;/li>
&lt;/ul>
&lt;h2 id="partitioning">Partitioning&lt;/h2>
&lt;p>If we look at older implementations of temporary tables, in the world of proprietary databases (Db2, SQL Server, Oracle), they generally store historical data in a separate physical table or partition, sometimes called a history table. In MariaDB this doesn’t happen automatically or by default, leaving the choice to the user. However, it seems to me a good idea in the general case to create one or more partitions to store historical rows. The main reason is that, rarely, a query has to read both historical and current data, and reading only one partition is an interesting optimization.&lt;/p>
&lt;h2 id="excluding-columns-from-versioning">Excluding columns from versioning&lt;/h2>
&lt;p>MariaDB allows us to exclude some columns from versioning. This means that if we update the values of those columns, we update the current row version in place rather than creating a new one. This is probably useful if a column is frequently updated and we don’t care about these changes. However, if we update more columns with one statement, and only a subset of them is excluded from versioning, a new row version is still created. All in all, the partial exclusion of some rows could be more confusing than useful in several cases.&lt;/p>
&lt;h2 id="replication">Replication&lt;/h2>
&lt;p>10.3 is a stable version, but it is still recent. Some of us adopt a new major version after some years, and we can even have reasons to stick with an old version. Furthermore, of course, many of us use MySQL, and MariaDB is not a drop-in replacement.&lt;/p>
&lt;p>But we can still enjoy temporal tables by adding a MariaDB 10.3 slave. I attached such a slave to older MariaDB versions, and to MySQL 5.6. In all tests, the feature behaved as expected.&lt;/p>
&lt;p>Initially, I was worried about replication lags. I assumed that, if replication lags, the slave applies the changes with a delay, and the timestamps in the tables are delayed accordingly. I am glad to say that I was wrong: the timestamps in temporal tables seem to match the ones in the binary log, so replication lags don’t affect their correctness.&lt;/p>
&lt;p>This is true both with row-based replication and with statement-based replication.&lt;/p>
&lt;p>A small caveat about temporal tables is that the version timestamps are only precise at second level. The fractional part should be ignored. You may have noticed this in the example at the beginning of this post.&lt;/p>
&lt;h2 id="backups">Backups&lt;/h2>
&lt;p>For backups you will need to use &lt;a href="https://mariadb.com/kb/en/library/mariabackup-overview/" target="_blank" rel="noopener noreferrer">mariabackup&lt;/a> instead of xtrabackup.&lt;/p>
&lt;p>mysqldump can be used, not necessarily from a MariaDB distribution. However, it treats temporal tables as regular tables. It does not backup historical data. This is necessary because of a design choice: we cannot insert rows with timestamps in the past. This makes temporal tables much more reliable. Also, temporal tables are likely to be (or become) quite big, so a dump is probably not the best way to backup them.&lt;/p>
&lt;p>&lt;em>–&lt;/em>&lt;/p>
&lt;p>&lt;em>Photo by &lt;a href="https://unsplash.com/photos/WeYamle9fDM?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Ashim D’Silva&lt;/a> on &lt;a href="https://unsplash.com/search/photos/canyon?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash&lt;/a>&lt;/em>&lt;/p></content:encoded><author>Federico Razzoli</author><category>MariaDB</category><category>MySQL</category><category>system-versioned tables</category><media:thumbnail url="https://percona.community/blog/2018/12/mariadb-system-versioned-tables_hu_67f40cbc67439966.jpg"/><media:content url="https://percona.community/blog/2018/12/mariadb-system-versioned-tables_hu_d2a41d9d1c19f4c6.jpg" medium="image"/></item><item><title>MySQL Setup at Hostinger Explained</title><link>https://percona.community/blog/2018/12/11/mysql-setup-hostinger-explained/</link><guid>https://percona.community/blog/2018/12/11/mysql-setup-hostinger-explained/</guid><pubDate>Tue, 11 Dec 2018 15:27:45 UTC</pubDate><description>Ever wondered how hosting companies manage their MySQL database architecture? At Hostinger, we have various MySQL setups starting from the standalone replica-less instances to Percona XtraDB Cluster (later just PXC), ProxySQL routing-based and even absolutely custom and unique solutions which I’m going to describe in this blog post.</description><content:encoded>&lt;p>Ever wondered how hosting companies manage their MySQL database architecture? At &lt;a href="https://www.hostinger.com/" target="_blank" rel="noopener noreferrer">Hostinger,&lt;/a> we have various MySQL setups starting from the standalone replica-less instances to &lt;a href="https://www.percona.com/software/mysql-database/percona-xtradb-cluster" target="_blank" rel="noopener noreferrer">Percona XtraDB Cluster&lt;/a> (later just PXC), &lt;a href="http://www.proxysql.com/" target="_blank" rel="noopener noreferrer">ProxySQL&lt;/a> routing-based and even absolutely custom and unique solutions which I’m going to describe in this blog post.&lt;/p>
&lt;p>We do not have elephant-sized databases for internal services like API, billing, and clients. Thus almost every decision ends up with high availability as a top priority instead of scalability.&lt;/p>
&lt;p>Still, scaling vertically is good enough for our case, as the database size does not exceed 500GB. One and the top requirements is the ability to access the master node, as we have fairly equal-distanced workloads for reading and writing.&lt;/p>
&lt;p>Our current setup for storing all the data about the clients, servers and so forth is using PXC formed of three nodes without any geo-replication. All nodes are running in the same datacenter.&lt;/p>
&lt;p>We have plans to migrate this cluster to geo-replicated cluster across three locations: the United States, Netherlands, and Singapore. This would allow us to warrant high availability if one of the locations became unreachable.&lt;/p>
&lt;p>Since PXC uses fully synchronous replication, there will be higher latencies for writes. But the reads will be much quicker because of the local replica in every location.&lt;/p>
&lt;p>We did some research on &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/group-replication.html" target="_blank" rel="noopener noreferrer">MySQL Group Replication&lt;/a>, but it requires instances to be closer to each other and is more sensitive to latencies.&lt;/p>
&lt;blockquote>
&lt;p>Group Replication is designed to be deployed in a cluster environment where server instances are very close to each other, and is impacted by both network latency as well as network bandwidth.&lt;/p>&lt;/blockquote>
&lt;p>PXC was used previously, thus we to know how to deal with it in critical circumstances and make it more highly available.&lt;/p>
&lt;p>In &lt;a href="https://www.000webhost.com/" target="_blank" rel="noopener noreferrer">000webhost.com&lt;/a> project and hAPI (Hostinger API) we use our aforementioned unique solution which selects the master node using Layer3 protocol.&lt;/p>
&lt;p>One of our best friends is BGP and BGP protocol, which is aged enough to buy its own beer, hence we use it a lot. This implementation also uses BGP as the underlying protocol and helps to point to the real master node. To run BGP protocol we use the ExaBGP service and announce VIP address as anycast from both master nodes.&lt;/p>
&lt;p>You should be asking: but how are you sure MySQL queries go to the one and the same instance instead of hitting both? We use &lt;a href="https://zookeeper.apache.org/doc/current/zookeeperOver.html" target="_blank" rel="noopener noreferrer">Zookeeper’s ephemeral nodes&lt;/a> to acquire the lock as mutually exclusive.&lt;/p>
&lt;p>Zookeeper acts like a circuit breaker between BGP speakers and the MySQL clients. If the lock is acquired we announce the VIP from the master node and applications send the queries toward this path. If the lock is released, another node can take it over and announce the VIP, so the application will send the queries without any efforts.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/12/mysql-setup-hostinger.jpg" alt="mysql setup at hostinger" />&lt;/figure>
&lt;em>MySQL Setup at Hostinger&lt;/em>&lt;/p>
&lt;p>The second question comes: what conditions should be met to stop announcing VIP? This can be implemented differently depending on use case, but we release the lock if MySQL process is down using systemd’s &lt;code>Requires&lt;/code> in the unit file of ExaBGP:&lt;/p>
&lt;blockquote>
&lt;p>Besides, with or without specifying After=, this unit will be stopped if one of the other units is explicitly stopped.&lt;/p>&lt;/blockquote>
&lt;p>With &lt;a href="https://www.freedesktop.org/wiki/Software/systemd/" target="_blank" rel="noopener noreferrer">systemd&lt;/a> we can create a nice dependency tree which ensures all of them are met. Stopping, killing, or even rebooting the MySQL will make systemd stop the ExaBGP process and withdraw the VIP announcement. The final result is a new master selected.&lt;/p>
&lt;p>We battle tested those master failovers during our &lt;a href="https://www.hostinger.com/blog/new-network-infrastructure" target="_blank" rel="noopener noreferrer">Gaming days&lt;/a> and nothing critical was noticed &lt;em>yet&lt;/em>.&lt;/p>
&lt;p>If you think good architecture is expensive, try bad architecture 😉&lt;/p>
&lt;p>– &lt;em>This post was originally published at &lt;a href="https://www.hostinger.com/blog/mysql-setup-at-hostinger-explained/" target="_blank" rel="noopener noreferrer">https://www.hostinger.com/blog/mysql-setup-at-hostinger-explained/&lt;/a> in June 2018. The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Donatas Abraitis</author><category>hosting</category><category>MySQL</category><category>ProxySQL</category><category>Tools</category><category>Zookeeper Cluster</category><media:thumbnail url="https://percona.community/blog/2018/12/mysql-setup-hostinger_hu_acb7bb452c7e7b06.jpg"/><media:content url="https://percona.community/blog/2018/12/mysql-setup-hostinger_hu_d028d027cd277e4d.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: ClickHouse at Messagebird: Analysing Billions of Events in Real-Time\*</title><link>https://percona.community/blog/2018/10/29/clickhouse-at-messagebird-analysing-billions-of-events-in-real-time/</link><guid>https://percona.community/blog/2018/10/29/clickhouse-at-messagebird-analysing-billions-of-events-in-real-time/</guid><pubDate>Mon, 29 Oct 2018 17:28:38 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/message-bird.png" alt="MessageBird Logo" />&lt;/figure>&lt;/p>
&lt;p>We’ll look into how Clickhouse allows us to ingest a large amount of data and run complex analytical interactive queries at &lt;a href="https://www.messagebird.com/en" target="_blank" rel="noopener noreferrer">MessageBird&lt;/a>. We also present the business needs that brought ClickHouse to our attention and detail the journey to its deployment. We cover the problems we faced, and how we dealt with them. We talk about our current Cloud production setup and how we deployed and use it.&lt;/p>
&lt;p>We are really enthusiastic to share a use case of Clickhouse, how it helped us to scale our analytics stack with the good, the bad and the ugly.&lt;/p>
&lt;p>The talk could be useful to newcomers and everyone wondering if Clickhouse could be useful to them.&lt;/p>
&lt;h3 id="what-were-looking-forward-to">What we’re looking forward to…&lt;/h3>
&lt;p>There are many talks, but these are among the top ones we’re looking forward to in particular:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/live/e18/sessions/a-year-in-google-cloud" target="_blank" rel="noopener noreferrer">A Year in Google Cloud&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/e18/sessions/advanced-features-of-clickhouse" target="_blank" rel="noopener noreferrer">Advanced Features of ClickHouse&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/e18/sessions/data-integrity-at-scale" target="_blank" rel="noopener noreferrer">Data Integrity at Scale&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/e18/sessions/prometheus-onto-being-boring" target="_blank" rel="noopener noreferrer">Prometheus, onto being boring&lt;/a>&lt;/li>
&lt;/ul></content:encoded><author>Félix Mattrat</author><author>Aleksandar Aleksandrov</author><category>big data</category><category>ClickHouse</category><category>Events</category><category>MySQL</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_b6a203b169366d6e.jpg"/><media:content url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_f7acbdfbcdd4a32e.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: pg_chameleon MySQL to PostgreSQL Replica Made Easy</title><link>https://percona.community/blog/2018/10/26/percona-live-europe-presents-pg_chameleon-mysql-postgresql-replica-made-easy/</link><guid>https://percona.community/blog/2018/10/26/percona-live-europe-presents-pg_chameleon-mysql-postgresql-replica-made-easy/</guid><pubDate>Fri, 26 Oct 2018 14:42:12 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/igor_small.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>What excites me is the possibility that this tool is giving to other people. Also, the challenges I’ve faced and the new ideas for the future releases are always source of interest that keep me focused on the project. So I’m looking forward to &lt;a href="https://www.percona.com/live/e18/sessions/pgchameleon-mysql-to-postgresql-replica-made-easy" target="_blank" rel="noopener noreferrer">sharing this with the conference delegates&lt;/a>.&lt;/p>
&lt;p>pg_chameleon can achieve two tasks in a very simple way. It can setup a permanent replica between MySQL and PostgreSQL, giving the freedom of choice for the right tool for the right job, or can migrate multiple schemas to a PostgreSQL database.&lt;/p>
&lt;p>Anybody that want to extend their database experience, taking the best of the two worlds, or who is seeking a simple way to migrate data with minimal downtime will find the presentation interesting.&lt;/p>
&lt;h3 id="what-else-am-i-looking-forward-to-at-percona-live-europe">What else am I looking forward to at Percona Live Europe?&lt;/h3>
&lt;p>I’m looking forward to Bruce Momjian’s &lt;a href="https://www.percona.com/live/e18/sessions/explaining-the-postgres-query-optimizer" target="_blank" rel="noopener noreferrer">Explaining the Postgres Query Optimizer&lt;/a>, Bo Wang’s &lt;a href="https://www.percona.com/live/e18/sessions/how-we-use-and-improve-percona-xtrabackup-at-alibaba-cloud" target="_blank" rel="noopener noreferrer">How we use and improve Percona XtraBackup at Alibaba Cloud&lt;/a> and Federico Razzoli’s &lt;a href="https://www.percona.com/live/e18/sessions/mariadb-system-versioned-tables" target="_blank" rel="noopener noreferrer">MariaDB system-versioned tables&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/blog/2018/08/17/replication-from-percona-server-for-mysql-to-postgresql-using-pg_chameleon/" target="_blank" rel="noopener noreferrer">Read the Percona blog&lt;/a> about pg_chameleon 
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/postgres-mysql-replication-using-pg_chameleon.png" alt=" " />&lt;/figure>&lt;/p></content:encoded><author>Federico Campoli</author><category>Events</category><category>MySQL</category><category>Percona Live Europe 2018</category><category>PostgreSQL</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2018/10/igor_small_hu_23c337dddb576606.jpg"/><media:content url="https://percona.community/blog/2018/10/igor_small_hu_adc5c6e00e2a4808.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: MariaDB 10.4 Reverse Privileges (DENY)</title><link>https://percona.community/blog/2018/10/23/mariadb-10-4-reverse-privileges-deny/</link><guid>https://percona.community/blog/2018/10/23/mariadb-10-4-reverse-privileges-deny/</guid><pubDate>Tue, 23 Oct 2018 11:41:48 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical.png" alt="MariaDB Foundation" />&lt;/figure>&lt;/p>
&lt;p>One of the most common questions about privileges in MySQL and MariaDB is how would a user revoke access to a particular table, in a large database with hundreds or thousands of tables, while keeping the rest available. Currently, there is no easy solution. Just grant access to everything else, individually. Not only does this reduce server performance, but is a nightmare to maintain. Reverse privileges solve this and more. And they are simple to explain to new admins too! So I look forward to sharing the knowledge during &lt;a href="https://www.percona.com/live/e18/sessions/mariadb-104-reverse-privileges-deny" target="_blank" rel="noopener noreferrer">my presentation at PLE18&lt;/a>.&lt;/p>
&lt;p>&lt;strong>DBAs&lt;/strong> would benefit from this talk the most. As it is a feature still under development, we are open for input from the community. Tell us what you think we should do to make this feature the best it can be.&lt;/p>
&lt;h2 id="what-im-looking-forward-to">What I’m looking forward to…&lt;/h2>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/vicentiu_ciorbaru-m18-2s.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>It will be quite interesting to see what challenges people have faced with MySQL and MariaDB and how they were overcome. As a database developer, it’s always important to understand how your users make use of the product. It is only through this that we can make it better.&lt;/p></content:encoded><author>Vicențiu Ciorbaru</author><category>Events</category><category>MariaDB</category><category>MySQL</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical_hu_752fc1922c64346b.jpg"/><media:content url="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical_hu_17205386f99e3d54.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: Need for speed - Boosting Apache Cassandra's performance using Netty</title><link>https://percona.community/blog/2018/10/22/percona-live-europe-presents-need-speed-boosting-apache-cassandras-performance-using-netty/</link><guid>https://percona.community/blog/2018/10/22/percona-live-europe-presents-need-speed-boosting-apache-cassandras-performance-using-netty/</guid><pubDate>Mon, 22 Oct 2018 08:34:35 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/apache-cassandra-logo-3.png" alt=" " />&lt;/figure>&lt;/p>
&lt;p>My talk is titled &lt;a href="https://www.percona.com/live/e18/sessions/need-for-speed-boosting-apache-cassandras-performance-using-netty" target="_blank" rel="noopener noreferrer">Need for speed: Boosting Apache Cassandra’s performance using Netty&lt;/a>. Over the years that I have worked in the software industry, making code run fast has fascinated me. So, naturally when I first started contributing to Apache Cassandra, I started looking opportunities to improve its performance. My talk takes us through some interesting challenges within a distributed system like &lt;a href="http://cassandra.apache.org/" target="_blank" rel="noopener noreferrer">Apache Cassandra&lt;/a> and various techniques to significantly improve its performance. Talking about performance is incredibly exciting because you can easily quantify and see the results. Making improvements to the database’s performance not only improves the user experience but also reflects positively on the organization’s bottom line. It also has the added benefit of pushing the boundaries of scale. Furthermore, my talk spans beyond Apache Cassandra and is generally applicable for writing performant networking applications in Java.&lt;/p>
&lt;h2 id="whod-benefit-most-from-the-presentation">Who’d benefit most from the presentation?&lt;/h2>
&lt;p>My talk is oriented primarily towards developers and operators. Although Apache Cassandra is written in Java and we talk about Netty, there is plenty in the talk that is generic and the lessons learned could be applied towards any Distributed System. I think developers with various experience levels would benefit from the talk. However, intermediate developers would benefit the most.&lt;/p>
&lt;h2 id="what-im-most-looking-forward-to-at-ple-18">What I’m most looking forward to at PLE ‘18…&lt;/h2>
&lt;p>There are many interesting sessions at the conference. Here are some of the interesting sessions -&lt;/p>
&lt;h4 id="performance-analyses-technologies-for-databases">&lt;a href="https://www.percona.com/live/e18/sessions/performance-analyses-technologies-for-databases" target="_blank" rel="noopener noreferrer">Performance Analyses Technologies for Databases&lt;/a>&lt;/h4>
&lt;p>As I mentioned, I am a big performance geek and in this talk Peter is going to talk about various methods to data infrastructure performance analysis including monitoring.&lt;/p>
&lt;h4 id="securing-access-to-facebook">&lt;a href="https://www.percona.com/live/e18/sessions/securing-access-to-facebooks-databases" target="_blank" rel="noopener noreferrer">Securing Access to Facebook’s Databases&lt;/a>&lt;/h4>
&lt;p>This is an interesting session from a security standpoint. Andrew is talking about securing access to MySQL. As most people know Facebook has a huge MySQL deployment and as security and privacy has become a prime concern, we see a lot of movement towards encryption. This talk is going to be particularly interesting because Facebook is using x509 client certs to authenticate. This is a non-trivial challenge for anybody at scale.&lt;/p>
&lt;h4 id="tls-for-mysql-at-large-scale">&lt;a href="https://www.percona.com/live/e18/sessions/tls-for-mysql-at-large-scale" target="_blank" rel="noopener noreferrer">TLS for MySQL at large scale&lt;/a>&lt;/h4>
&lt;p>This talk from Wikipedia is along similar lines as the previous one. It just goes to emphasize the importance of security in today’s climate. What’s interesting is that Wikipedia and Facebook, both are talking about it! I am curious to find out what sort of privacy challenges Wikipedia is solving.&lt;/p>
&lt;h4 id="advanced-mysql-data-at-rest-encryption-in-percona-server">&lt;a href="https://www.percona.com/live/e18/sessions/advanced-mysql-data-at-rest-encryption-in-percona-server" target="_blank" rel="noopener noreferrer">Advanced MySQL Data at Rest Encryption in Percona Server&lt;/a>&lt;/h4>
&lt;p>Another security related talk! This one’s about encryption at rest. This is interesting in and of itself as we tend to talk a lot about security in transit and less often about security of data at rest. I hope to learn more about the cost of implementing encryption at rest and its impact on the database performance, operations as well as security.&lt;/p>
&lt;h4 id="artificial-intelligence-database-performance-tuning">&lt;a href="https://www.percona.com/live/e18/sessions/artificial-intelligence-database-performance-tuning" target="_blank" rel="noopener noreferrer">Artificial Intelligence Database Performance Tuning&lt;/a>&lt;/h4>
&lt;p>I think this is an exciting time for the database industry as we’ve not only seen large increase in data volumes but also user expectations have gone up around performance. So, can AI help us tune our databases? Traditionally, the domain of an experienced DBA, I think AI can help us deliver better performance. This talk is about using Genetic Algorithms to tune the database performance. I am curious to find out how these algorithms are applied to tune databases.&lt;/p></content:encoded><author>Dinesh Joshi</author><category>DevOps</category><category>Events</category><category>MySQL</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/apache-cassandra-logo-3_hu_5fd86578ee1f1a14.jpg"/><media:content url="https://percona.community/blog/2018/10/apache-cassandra-logo-3_hu_5a14f5eb72076809.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: The Latest MySQL Replication Features</title><link>https://percona.community/blog/2018/10/19/latest-mysql-replication-features/</link><guid>https://percona.community/blog/2018/10/19/latest-mysql-replication-features/</guid><pubDate>Fri, 19 Oct 2018 15:06:19 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo.png" alt="PLE Frankfurt Logo" />&lt;/figure>&lt;/p>
&lt;p>Considering the modern world of technology, where distributed system play a key role, replication in MySQL® is at the very heart of that change. It is very exciting to deliver &lt;a href="https://www.percona.com/live/e18/sessions/the-latest-mysql-replication-features" target="_blank" rel="noopener noreferrer">this presentation&lt;/a> and to be able to show everyone the greatest and the latest features that MySQL brings in order to continue the success that it has always been in the past.&lt;/p>
&lt;p>The talk is suitable for anyone that’s interested in knowing what Oracle is doing with MySQL replication. Old acquaintances will get familiarized about new features already delivered and being considered and newcomers to the MySQL ecosystem will see how great MySQL Replication has grown to be and how it fits in their business..&lt;/p>
&lt;h2 id="what-im-most-looking-forward-to-at-percona-live-europe">What I’m most looking forward to at Percona Live Europe…&lt;/h2>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/tiago-jorge.jpg" alt="tiago jorge" />&lt;/figure>&lt;/p>
&lt;p>We are always eager to get feedback about the product.&lt;/p>
&lt;p>Moreover, MySQL being MySQL has a very large user base and, as such, is deployed and used in many different ways. It is very appealing and useful to continuously learn how our customers and users are making the most out of the product. Especially when it comes to replication, since MySQL replication infrastructure is anenabler for advanced and complex setups, making it a powerful and indispensable tool in virtually any setup nowadays.&lt;/p></content:encoded><author>Tiago Jorge</author><category>Events</category><category>MySQL</category><category>Oracle</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_b6a203b169366d6e.jpg"/><media:content url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_f7acbdfbcdd4a32e.jpg" medium="image"/></item><item><title>Percona Live Europe Presents: MariaDB System-Versioned Tables</title><link>https://percona.community/blog/2018/10/17/percona-live-europe-presents-mariadb-system-versioned-tables/</link><guid>https://percona.community/blog/2018/10/17/percona-live-europe-presents-mariadb-system-versioned-tables/</guid><pubDate>Wed, 17 Oct 2018 16:14:38 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo.png" alt="PLE Frankfurt Logo" />&lt;/figure>&lt;/p>
&lt;p>System-versioned tables, or temporal tables, are a typical feature of proprietary database management systems like DB2, Oracle and SQL Server. They also appeared at some point in PostgreSQL, but only as an extension; and also in CockroachDB, but in a somewhat limited fashion.&lt;/p>
&lt;p>The MariaDB® implementation is the first appearance of temporal tables in the MySQL ecosystem, and the most complete implementation in the open source world.&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/live/e18/sessions/mariadb-system-versioned-tables" target="_blank" rel="noopener noreferrer">My presentation&lt;/a> will be useful for &lt;strong>analysts&lt;/strong>, and some &lt;strong>managers&lt;/strong>, who will definitely benefit from learning how to use temporal tables. Statistics about how data evolves over time is an important part of their job. This feature will allow them to query data as it was at a certain point in time. Or to query how data changed over a period, including rows that were added, deleted or modified.&lt;/p>
&lt;p>&lt;strong>Developers&lt;/strong> will also find this feature useful, if they deal with data versioning or auditing. Recording the evolution of data into a database is not easy - several solutions are possible, but none is perfect. Streaming data changes to some event-based technology is also complex, and sometimes it’s simply a waste of resources. System-versioned tables are a good solution for many use cases.&lt;/p>
&lt;p>And of course, &lt;strong>DBA’s&lt;/strong>. Those guys will need to know what this feature is about, suggest it when appropriate, and maintain it in production systems.&lt;/p>
&lt;p>More generally, many people are interested in understanding MariaDB’s unique features, as well as its MySQL ones. Their approach allows them to choose “the right tool for the right purpose”.&lt;/p>
&lt;h4 id="what-im-looking-forward-to">What I’m looking forward to…&lt;/h4>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/federico-razzoli.jpg" alt="federico razzoli" />&lt;/figure>&lt;/p>
&lt;p>I am excited about Percona Live agenda. A session that I definitely want to attend is &lt;strong>&lt;a href="https://www.percona.com/live/e18/sessions/demystifying-mysql-replication-crash-safety" target="_blank" rel="noopener noreferrer">MySQL Replication Crash Safety&lt;/a>&lt;/strong>. I find extremely useful and interesting the talks about technology limitations and flaws. Jean-François has a long series of writings on MySQL replication and crash-safety, and I have questions for him.&lt;/p>
&lt;p>I also like the evolution that PMM and its components had over the years. I want to understand how to use them at best in my new job, so I am glad to see that there will be several sessions on the topic. I plan to attend some sessions about PMM and Prometheus.&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://www.percona.com/live/e18/sessions/performance-analyses-technologies-for-databases" target="_blank" rel="noopener noreferrer">Performance Analyses Technologies for Databases&lt;/a>&lt;/strong> makes me think to the cases when I saw a technology evaluated in an inappropriate way, and the talks I had with people impressed by some blog posts showing impressive benchmarks which didn’t fully understand. I will definitely attend.&lt;/p>
&lt;p>And finally, I plan to learn something about &lt;strong>&lt;a href="https://www.percona.com/live/e18/sessions/advanced-features-of-clickhouse" target="_blank" rel="noopener noreferrer">ClickHouse&lt;/a>&lt;/strong>, &lt;a href="https://www.percona.com/live/e18/sessions/myrocks-production-case-studies-at-facebook" target="_blank" rel="noopener noreferrer">&lt;strong>MyRocks&lt;/strong>&lt;/a> and &lt;a href="https://www.percona.com/live/e18/sessions/tidb-distributed-horizontally-scalable-mysql-compatible" target="_blank" rel="noopener noreferrer">&lt;strong>TiDB&lt;/strong>&lt;/a>.&lt;/p>
&lt;p>See you there!&lt;/p></content:encoded><author>Federico Razzoli</author><category>Events</category><category>MariaDB</category><category>MySQL</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_b6a203b169366d6e.jpg"/><media:content url="https://percona.community/blog/2018/10/PLE-Frankfurt-Logo_hu_f7acbdfbcdd4a32e.jpg" medium="image"/></item><item><title>Export to JSON from MySQL All Ready for MongoDB</title><link>https://percona.community/blog/2018/10/16/export-to-json-from-mysql-all-ready-for-mongodb/</link><guid>https://percona.community/blog/2018/10/16/export-to-json-from-mysql-all-ready-for-mongodb/</guid><pubDate>Tue, 16 Oct 2018 15:18:36 UTC</pubDate><description>This post walks through how to export data from MySQL® into JSON format, ready to ingest into MongoDB®. Starting from MySQL 5.7+, there is native support for JSON. MySQL provides functions that actually create JSON values, so I will be using these functions in this article to export to JSON from MySQL:</description><content:encoded>&lt;p>This post walks through how to export data from &lt;a href="https://dev.mysql.com/" target="_blank" rel="noopener noreferrer">MySQL&lt;/a>® into JSON format, ready to ingest into &lt;a href="https://www.mongodb.com/" target="_blank" rel="noopener noreferrer">MongoDB&lt;/a>®. Starting from MySQL 5.7+, there is native support for JSON. MySQL provides functions that actually create JSON values, so I will be using these functions in this article to export to JSON from MySQL:&lt;/p>
&lt;ul>
&lt;li>JSON_OBJECT&lt;/li>
&lt;li>JSON_ARRAY&lt;/li>
&lt;/ul>
&lt;p>These functions make it easy to convert MySQL data to JSON 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">mysql> SELECT json_object('employee_id', emp_no, 'first_name', first_name ) AS 'JSON' FROM employees LIMIT 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">| 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">| {"first_name": "Aamer", "employee_id": 444117} |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| {"first_name": "Aamer", "employee_id": 409151} |
&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>In this article, I will be using the employees sample database available from here: &lt;a href="https://dev.mysql.com/doc/employee/en/employees-installation.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/employee/en/employees-installation.html&lt;/a>&lt;/p>
&lt;p>You can find the employees schema on &lt;a href="https://dev.mysql.com/doc/employee/en/images/employees-schema.png" target="_blank" rel="noopener noreferrer">dev.mysql.com&lt;/a>.&lt;/p>
&lt;p>When mapping relations with collections, generally there is no one to one mapping, you would want to merge data from some MySQL tables into a single collection.&lt;/p>
&lt;h2 id="export-data-to-json-format">Export data to JSON format&lt;/h2>
&lt;p>To export data, I have constructed the following SQL (the data is combined from 3 different tables: employees, salaries, and departments):&lt;/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 json_pretty(json_object(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'emp_no', emp.emp_no,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'first_name', emp.first_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'last_name', emp.last_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'hire_date',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">json_object("$date", DATE_FORMAT(emp.hire_date,'%Y-%m-%dT%TZ')),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'Department', JSON_ARRAY(json_object('dept_id', dept.dept_no, 'dept_name', dept.dept_name)),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'Salary', s.salary)) AS 'json'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM employees emp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN salaries s ON s.emp_no=emp.emp_no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN current_dept_emp c on c.emp_no = emp.emp_no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN departments dept on dept.dept_no = c.dept_no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">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">Output:
&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 ***************************
&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">"Salary": 60117,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"emp_no": 10001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"hire_date": "1986-06-26",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"last_name": "Facello",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"Department": [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"dept_id": "d005",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"dept_name": "Development"
&lt;/span>&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">"first_name": "Georgi"
&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>You can see from this that json_object did not convert ‘hire_date’ column value to be compatible with MongoDB.  We have to convert date into ISODate format:&lt;/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> select json_object('hire_date', hire_date) AS "Original Date", json_object('hire_date', DATE_FORMAT(hire_date,'%Y-%m-%dT%TZ')) AS "ISODate" from employees 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">| Original Date | ISODate |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-----------------------------+---------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| {"hire_date": "1985-01-01"} | {"hire_date": "1985-01-01T00:00:00Z"} |
&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>Next, we dump the output to a file (the above query is slightly modified) 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-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 json_object(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'emp_no', emp.emp_no,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'first_name', emp.first_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'last_name', emp.last_name,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'hire_date', json_object("$date", DATE_FORMAT(emp.hire_date,'%Y-%m-%dT%TZ')),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'Department', JSON_ARRAY(json_object('dept_id', dept.dept_no, 'dept_name', dept.dept_name)),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">'Salary', s.salary) as 'json'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INTO OUTFILE 'C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/employees.json' ## IMPORTANT you may want to adjust outfile path here
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM employees emp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN salaries s ON s.emp_no=emp.emp_no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN current_dept_emp c on c.emp_no = emp.emp_no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INNER JOIN departments dept on dept.dept_no = c.dept_no&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="importing-data">Importing data&lt;/h2>
&lt;p>To load the file employees.json  into MongoDB, I use the &lt;a href="https://docs.mongodb.com/manual/reference/program/mongoimport/" target="_blank" rel="noopener noreferrer">mongoimport&lt;/a> utility.  It’s a multi-threaded tool that can load large files efficiently.&lt;/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"># mongoimport --db test --collection employees --drop &lt; employees.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:30.401+0100 connected to: localhost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:30.401+0100 dropping: test.employees
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:33.400+0100 test.employees 34.0MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:36.401+0100 test.employees 67.3MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:39.399+0100 test.employees 100MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:42.400+0100 test.employees 134MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:45.401+0100 test.employees 168MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:48.402+0100 test.employees 202MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:51.402+0100 test.employees 235MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:54.400+0100 test.employees 269MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:32:57.400+0100 test.employees 303MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:00.403+0100 test.employees 335MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:03.404+0100 test.employees 368MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:06.399+0100 test.employees 397MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:09.400+0100 test.employees 430MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:12.400+0100 test.employees 465MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:15.403+0100 test.employees 499MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:18.401+0100 test.employees 530MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:18.589+0100 test.employees 533MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2018-10-05T12:33:18.589+0100 imported 2844047 documents&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="validate">Validate&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-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">> db.employees.find({}).pretty()
&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("5bb740cfd73e26bf45435181"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"Salary" : 60117,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"emp_no" : 10001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"hire_date" : ISODate("1986-06-26T00:00:00Z"),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"last_name" : "Facello",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"Department" : [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"dept_id" : "d005",
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">"dept_name" : "Development"
&lt;/span>&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">"first_name" : "Georgi"
&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 successfully migrated some data from MySQL to MongoDB!&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Aftab Khan</author><category>Entry Level</category><category>export data</category><category>MongoDB</category><category>MySQL</category><category>tools</category><category>Tools</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>Percona Live Europe Session: What's New in MariaDB Server 10.3</title><link>https://percona.community/blog/2018/10/16/percona-live-europe-session-whats-new-mariadb-server-10-3/</link><guid>https://percona.community/blog/2018/10/16/percona-live-europe-session-whats-new-mariadb-server-10-3/</guid><pubDate>Tue, 16 Oct 2018 12:42:19 UTC</pubDate><description>**</description><content:encoded>&lt;p>**
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical.png" alt="MariaDB Foundation" />&lt;/figure>&lt;/p>
&lt;p>Having spent my recent years “in the real world”, working with many users, I’ve learnt that a particular new feature does not necessarily excite users as much as one might expect. MariaDB 10.3 however actually has some very interesting features that users do get excited about.&lt;/p>
&lt;p>So that’s great!&lt;/p>
&lt;p>&lt;a href="https://www.percona.com/live/e18/sessions/whats-new-in-and-around-mariadb-server-103" target="_blank" rel="noopener noreferrer">My session at Percona Live Europe in Frankfurt&lt;/a> is going to be best for people deploying MariaDB or related infra, who haven’t had a chance to explore what the various features actually mean, or what they can do with them. The presentation will provide some practical examples to guide that process.&lt;/p>
&lt;h3 id="what-im-most-looking-forward-to">What I’m most looking forward to…&lt;/h3>
&lt;p>Given my new position as CEO of the &lt;a href="https://mariadb.org/about/" target="_blank" rel="noopener noreferrer">MariaDB Foundation&lt;/a>, I’m most looking forward to meeting lots of people. Many I know from way back and it will be good to catch up, others I haven’t met yet. The program looks fabulous, but I expect to spend a lot of time in the “hallway track”, and doing a lot of listening.&lt;/p>
&lt;p>Arjen Lentz is an old hand from the early and golden MySQL AB eras. After the acquisition of his company Open Query, he is now once again accumulating jetlag. This time as CEO of the MariaDB Foundation, eager to meet people the world over and talk about the MariaDB ecosystem.&lt;/p>
&lt;p>Arjen was responding to our questions about his session at the forthcoming &lt;a href="https://www.percona.com/live/e18/" target="_blank" rel="noopener noreferrer">Percona Live Europe 2018&lt;/a> conference in Frankfurt. _&lt;/p></content:encoded><author>Arjen Lentz</author><category>Events</category><category>MariaDB</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical_hu_752fc1922c64346b.jpg"/><media:content url="https://percona.community/blog/2018/10/MariaDB-Foundation-vertical_hu_17205386f99e3d54.jpg" medium="image"/></item><item><title>Percona Live Europe Tutorial: Query Optimization and TLS at Large Scale</title><link>https://percona.community/blog/2018/10/15/percona-live-europe-tutorial-query-optimization-workshop-tls-large-scale-session/</link><guid>https://percona.community/blog/2018/10/15/percona-live-europe-tutorial-query-optimization-workshop-tls-large-scale-session/</guid><pubDate>Mon, 15 Oct 2018 14:05:06 UTC</pubDate><description> For Percona Live Europe this year, I got accepted a workshop on query optimization and a 50-minute talk covering TLS for MySQL at Large Scale, talking about our experiences at the Wikimedia Foundation.</description><content:encoded>&lt;p>&lt;a href="https://www.percona.com/live/e18/sessions/tls-for-mysql-at-large-scale" target="_blank" rel="noopener noreferrer">
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/MySQL-at-scale.jpg" alt="MySQL has many ways to provide scalability, but can it provide it while at the same time guarantee perfect privacy? Learn it at my tutorial!" />&lt;/figure>&lt;/a> For Percona Live Europe this year, &lt;a href="https://www.percona.com/live/e18/speaker/jaime-crespo" target="_blank" rel="noopener noreferrer">I got accepted&lt;/a> a workshop on query optimization and a 50-minute talk covering TLS for MySQL at Large Scale, talking about our experiences at the &lt;a href="https://wikimediafoundation.org/" target="_blank" rel="noopener noreferrer">Wikimedia Foundation&lt;/a>.&lt;/p>
&lt;h3 id="workshop">Workshop&lt;/h3>
&lt;p>The 3-hour workshop on Monday, titled &lt;a href="https://www.percona.com/live/e18/sessions/query-optimization-with-mysql-80-and-mariadb-103-the-basics" target="_blank" rel="noopener noreferrer">&lt;em>&lt;strong>Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics&lt;/strong>&lt;/em>&lt;/a> is a beginners’ tutorial–though dense in content. It’s for people who are more familiar with database storage systems other than InnoDB for MySQL, MariaDB or Percona Server. Or who, already familiar with them, are suffering performance and scaling issues with their SQL queries. If you get confused with the output of basic commands like EXPLAIN and SHOW STATUS and want to learn some SQL-level optimizations, such as creating the right indexes or altering the schema to get the most out of the performance of your database server, then you want to attend this tutorial before going into more advanced topics. Even veteran DBAs and developers may learn one or two new tricks, only available on the latest server versions!&lt;/p>
&lt;p>Something that people may enjoy is that, during the tutorial, every attendee will be able to throw queries to a real-time copy of the Wikipedia database servers—or setup their own offline Wikipedia copy in their laptop. They’ll get practice by themselves what is being explained—so it will be fully hands-on. I like my sessions to be interactive, so all attendees should get ready to answer questions and think through the proposed problems by themselves!&lt;/p>
&lt;h3 id="fifty-minutes-talk">Fifty minutes talk&lt;/h3>
&lt;p>My 50 minute talk &lt;a href="https://www.percona.com/live/e18/sessions/tls-for-mysql-at-large-scale" target="_blank" rel="noopener noreferrer">&lt;em>&lt;strong>TLS for MySQL at Large Scale&lt;/strong>&lt;/em>&lt;/a> will be a bit more advanced, although maybe more attractive to users of other database technologies. On Tuesday, I will tell the tale of the mistakes and lessons learned while deploying encryption (TLS/SSL) for the replication, administration, and client connections of our databases. At the Wikimedia Foundation we take very seriously the privacy of our users—Wikipedia readers, project contributors, data reusers and every members of our community—and while none of our databases are publicly reachable, our aim is to encrypt every single connection between servers, even within our datacenters.&lt;/p>
&lt;p>However, when people talk about security topics, most of the time they are trying to show off the good parts of their set up, while hiding the ugly parts. Or maybe they are too theoretical to actually learn something. My focus will not be on the security principles everybody should follow, but on the pure operational problems, and the solutions we needed to deploy, as well what we would have done differently if we had known, while deploying TLS on our 200+ MariaDB server pool.&lt;/p>
&lt;h3 id="looking-forward">Looking forward…&lt;/h3>
&lt;p>For me, as an attendee, I always look forward to the &lt;a href="https://www.percona.com/live/e18/speaker/ren-canna" target="_blank" rel="noopener noreferrer">ProxySQL sessions&lt;/a>, as it is something we are currently deploying in our production. Also, I want to know more about the maturity and roadmap of the newest &lt;a href="https://www.percona.com/live/e18/sessions/mysql-80-performance-scalability-benchmarks" target="_blank" rel="noopener noreferrer">MySQL&lt;/a> and &lt;a href="https://www.percona.com/live/e18/sessions/whats-new-in-and-around-mariadb-server-103" target="_blank" rel="noopener noreferrer">MariaDB&lt;/a> releases, as they keep adding new interesting features we need, as well as cluster technologies such as Galera and &lt;a href="https://www.percona.com/live/e18/sessions/the-latest-mysql-replication-features" target="_blank" rel="noopener noreferrer">InnoDB Cluster&lt;/a>. I like, too, to talk with people developing and using other technologies outside of my stack, and you never know when they will fill in a need we have (&lt;a href="https://www.percona.com/live/e18/sessions/clickhouse-at-messagebird-analysing-billions-of-events-in-real-time" target="_blank" rel="noopener noreferrer">analytics&lt;/a>, &lt;a href="https://www.percona.com/live/e18/sessions/myrocks-production-case-studies-at-facebook" target="_blank" rel="noopener noreferrer">compression&lt;/a>, &lt;a href="https://www.percona.com/live/e18/sessions/sharedrocks-a-scalable-master-slave-replication-with-rocksdb-and-shared-file-storage" target="_blank" rel="noopener noreferrer">NoSQL&lt;/a>, etc.).&lt;/p>
&lt;p>But above all, the thing I enjoy the most is the networking—being able to talk with professionals that suffer the same problems that I do is something I normally cannot do, and that I enjoy doing a lot during Percona Live. [caption id=“attachment_390” align=“alignright” width=“808”]&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/jaime_crespo_2018.jpeg" alt="Jaime Crespo" />&lt;/figure>&lt;/p>
&lt;p>&lt;em>Jaime Crespo in a Percona Live T-Shirt - why not come to this year’s event and start YOUR collection.&lt;/em> [/caption]&lt;/p></content:encoded><author>Jaime Crespo</author><category>Events</category><category>MariaDB</category><category>MySQL</category><category>Open Source Databases</category><category>Percona Live Europe 2018</category><media:thumbnail url="https://percona.community/blog/2018/10/MySQL-at-scale_hu_3c5128ac9f54aa12.jpg"/><media:content url="https://percona.community/blog/2018/10/MySQL-at-scale_hu_ec646f6415dc7148.jpg" medium="image"/></item><item><title>Generating Identifiers – from AUTO_INCREMENT to Sequence</title><link>https://percona.community/blog/2018/10/12/generating-identifiers-auto_increment-sequence/</link><guid>https://percona.community/blog/2018/10/12/generating-identifiers-auto_increment-sequence/</guid><pubDate>Fri, 12 Oct 2018 11:00:58 UTC</pubDate><description>There are a number of options for generating ID values for your tables. In this post, Alexey Mikotkin of Devart explores your choices for generating identifiers with a look at auto_increment, triggers, UUID and sequences.</description><content:encoded>&lt;p>There are a number of options for generating ID values for your tables. In this post, Alexey Mikotkin of Devart explores your choices for generating identifiers with a look at auto_increment, triggers, UUID and sequences.&lt;/p>
&lt;h2 id="auto_increment">AUTO_INCREMENT&lt;/h2>
&lt;p>Frequently, we happen to need to fill tables with unique identifiers. Naturally, the first example of such identifiers is PRIMARY KEY data. These are usually integer values hidden from the user since their specific values are unimportant.&lt;/p>
&lt;p>When adding a row to a table, you need to take this new key value from somewhere. You can set up your own process of generating a new identifier, but MySQL comes to the aid of the user with the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html" target="_blank" rel="noopener noreferrer">AUTO_INCREMENT&lt;/a> column setting. It is set as a column attribute and allows you to generate unique integer identifiers. As an example, consider the &lt;code>**users**&lt;/code> table, the primary key includes an &lt;code>**id**&lt;/code> column of type INT:&lt;/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 users (
&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"> first_name varchar(100) NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> last_name varchar(100) NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> email varchar(254) 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;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Inserting a NULL value into the &lt;code>**id**&lt;/code> field leads to the generation of a unique value; inserting 0 value is also possible unless the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_auto_value_on_zero" target="_blank" rel="noopener noreferrer">NO_AUTO_VALUE_ON_ZERO&lt;/a> Server SQL Mode is enabled:&lt;/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">INSERT INTO users(id, first_name, last_name, email) VALUES (NULL, 'Simon', 'Wood', 'simon@testhost.com');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO users(id, first_name, last_name, email) VALUES (0, 'Peter', 'Hopper', 'peter@testhost.com');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It is possible to omit the &lt;code>**id**&lt;/code> column. The same result is obtained 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-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">INSERT INTO users(first_name, last_name, email) VALUES ('Simon', 'Wood', 'simon@testhost.com');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO users(first_name, last_name, email) VALUES ('Peter', 'Hopper', 'peter@testhost.com');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The selection will provide the following result:
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/select-from-users-table.png" alt="select from users table in dbForge studio" />&lt;/figure>
&lt;em>Select from users table shown in dbForge Studio&lt;/em>&lt;/p>
&lt;p>You can get the automatically generated value using the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id" target="_blank" rel="noopener noreferrer">LAST_INSERT_ID()&lt;/a> session function. This value can be used to insert a new row into a related table.&lt;/p>
&lt;p>There are aspects to consider when using AUTO_INCREMENT, here are some:&lt;/p>
&lt;ul>
&lt;li>In the case of rollback of a data insertion transaction, no data will be added to a table. However, the AUTO_INCREMENT counter will increase, and the next time you insert a row in the table, holes will appear in the table.&lt;/li>
&lt;li>In the case of multiple data inserts with a single INSERT command, the LAST_INSERT_ID() function will return an automatically generated value for the first row.&lt;/li>
&lt;li>The problem with the AUTO_INCREMENT counter value is described in &lt;a href="https://bugs.mysql.com/bug.php?id=199" target="_blank" rel="noopener noreferrer">Bug #199 - Innodb autoincrement stats los on restart&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>For example, let’s consider several cases of using AUTO_INCREMENT for &lt;code>**table1**&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">CREATE TABLE table1 (
&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"> 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">ENGINE = INNODB; -- transactional 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">-- Insert operations.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT LAST_INSERT_ID() INTO @p1; -- 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">-- Insert operations within commited transaction.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">START TRANSACTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">COMMIT;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT LAST_INSERT_ID() INTO @p3; -- 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">-- Insert operations within rolled back transaction.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">START TRANSACTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 7 won't be inserted (hole)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 8 won't be inserted (hole)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL); -- 9 won't be inserted (hole)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ROLLBACK;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT LAST_INSERT_ID() INTO @p2; -- 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">-- Insert multiple rows operation.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table1 VALUES (NULL), (NULL), (NULL); -- 10, 11, 12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT LAST_INSERT_ID() INTO @p4; -- 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">-- Let’s check which LAST_INSERT_ID() values were at different stages of the script execution:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT @p1, @p2, @p3, @p4;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------+------+------+------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| @p1 | @p2 | @p3 | @p4 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+------+------+------+------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 3 | 9 | 6 | 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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- The data selection from the table shows that there are holes in the table in the values of identifiers:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SELECT * FROM table1;
&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>&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">| 3 |
&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">| 5 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 6 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 10 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 11 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 12 |
&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 next AUTO_INCREMENT value for the table can be parsed from the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/show-create-table.html" target="_blank" rel="noopener noreferrer">SHOW CREATE TABLE&lt;/a> result or read from the AUTO_INCREMENT field of the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/tables-table.html" target="_blank" rel="noopener noreferrer">INFORMATION_SCHEMA TABLES&lt;/a> table.&lt;/p>
&lt;p>The rarer case is when the primary key is surrogate — it consists of two columns. The &lt;strong>MyISAM engine&lt;/strong> has an interesting solution that provides the possibility of generating values for such keys. Let’s consider the 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">CREATE TABLE roomdetails (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> room char(30) NOT NULL,
&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">PRIMARY KEY (room, 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">ENGINE = MYISAM;
&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 roomdetails VALUES ('ManClothing', NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO roomdetails VALUES ('WomanClothing', NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO roomdetails VALUES ('WomanClothing', NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO roomdetails VALUES ('WomanClothing', NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO roomdetails VALUES ('Fitting', NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO roomdetails VALUES ('ManClothing', NULL);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It is quite a convenient solution:
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/select-from-roomdetails-table.png" alt="select from roomdetails table" />&lt;/figure>&lt;/p>
&lt;h3 id="special-values-auto-generation">Special values auto generation&lt;/h3>
&lt;p>The possibilities of the AUTO_INCREMENT attribute are limited because it can be used only for generating simple integer values. But what about complex identifier values? For example, depending on the date/time or [A0001, A0002, B0150…]). To be sure, such values should not be used in primary keys, but they might be used for some auxiliary identifiers.&lt;/p>
&lt;p>The generation of such unique values can be automated, but it will be necessary to write code for such purposes. We can use the &lt;strong>BEFORE INSERT&lt;/strong> trigger to perform the actions we need.&lt;/p>
&lt;p>Let’s consider a simple example. We have the &lt;code>**sensors&lt;/code>** table for sensors registration. Each sensor in the table has its own name, location, and type: 1 –analog, 2 –discrete, 3 –valve. Moreover, each sensor should be marked with a unique label like [symbolic representation of the sensor type + a unique 4-digit number] where the symbolic representation corresponds to such values [AN, DS, VL].&lt;/p>
&lt;p>In our case, it is necessary to form values like these [DS0001, DS0002…] and insert them into the &lt;code>**label&lt;/code>** column.&lt;/p>
&lt;p>When the trigger is executed, it is necessary to understand if any sensors of this type exist in the table. It is enough to assign number “1” to the first sensor of a certain type when it is added to the table.&lt;/p>
&lt;p>In case such sensors already exist, it is necessary to find the maximum value of the identifier in this group and form a new one by incrementing the value by 1. Naturally, it is necessary to take into account that the label should start with the desired symbol and the number should be 4-digit.&lt;/p>
&lt;p>So, here is the table and the trigger creation script:&lt;/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">CREATE TABLE sensors (
&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"> type int NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name varchar(255) DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> `position` int DEFAULT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> label char(6) 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">DELIMITER $$
&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 trigger_sensors
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BEFORE INSERT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ON sensors
&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"> IF (NEW.label IS NULL) THEN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -- Find max existed label for specified sensor type
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SELECT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MAX(label) INTO @max_label
&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"> sensors
&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"> type = NEW.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 (@max_label IS NULL) THEN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SET @label =
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CASE NEW.type
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WHEN 1 THEN 'AN'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WHEN 2 THEN 'DS'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WHEN 3 THEN 'VL'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ELSE 'UNKNOWN'
&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"> -- Set first sensor label
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SET NEW.label = CONCAT(@label, '0001');
&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"> -- Set next sensor label
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SET NEW.label = CONCAT(SUBSTR(@max_label, 1, 2), LPAD(SUBSTR(@max_label, 3) + 1, 4, '0'));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> END IF;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> END IF;
&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">DELIMITER;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The code for generating a new identifier can, of course, be more complex. In this case, it is desirable to implement some of the code as a stored procedure/function. Let’s try to add several sensors to the table and look at the result of the labels generation:&lt;/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">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 1, 'temperature 1', 10, 'AN0025'); -- Set exact label value 'AN0025'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 1, 'temperature 2', 11, NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 1, 'pressure 1', 15, NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 2, 'door 1', 10, NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 2, 'door 2', 11, NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 3, 'valve 1', 20, NULL);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO sensors (id, type, name, `position`, label) VALUES (NULL, 3, 'valve 2', 21, NULL);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/generating-complex-sequences.png" alt="generating complex keys" />&lt;/figure>&lt;/p>
&lt;h3 id="using-uuid">Using UUID&lt;/h3>
&lt;p>Another version of the identification data is worth mentioning - Universal Unique Identifier (UUID), also known as GUID. This is a 128-bit number suitable for use in primary keys.&lt;/p>
&lt;p>A UUUI value can be represented as a string - CHAR(36)/VARCHAR(36) or a binary value - BINARY(16). Benefits:&lt;/p>
&lt;ul>
&lt;li>Ability to generate values ​​from the outside, for example from an application.&lt;/li>
&lt;li>UUID values ​​are unique across tables and databases since the standard assumes uniqueness in space and time.&lt;/li>
&lt;li>There is a specification - &lt;a href="http://www.ietf.org/rfc/rfc4122.txt" target="_blank" rel="noopener noreferrer">A Universally Unique IDentifier (UUID) URN Namespace&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Disadvantages:&lt;/p>
&lt;ul>
&lt;li>Possible performance problems.&lt;/li>
&lt;li>Data increase.&lt;/li>
&lt;li>More complex data analysis (debugging).&lt;/li>
&lt;/ul>
&lt;p>To generate this value, MySQL function &lt;strong>UUID()&lt;/strong> is used. New functions have been added to Oracle MySQL 8.0 server to work with UUID values ​​- UUID_TO_BIN, BIN_TO_UUID, IS_UUID. Learn more about it at the Oracle MySQL website - &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid" target="_blank" rel="noopener noreferrer">UUID()&lt;/a>&lt;/p>
&lt;p>The code shows the use of UUID values:&lt;/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">CREATE TABLE table_uuid (id binary(16) 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">INSERT INTO table_uuid VALUES(UUID_TO_BIN(UUID()));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table_uuid VALUES(UUID_TO_BIN(UUID()));
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO table_uuid VALUES(UUID_TO_BIN(UUID()));
&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 BIN_TO_UUID(id) FROM table_uuid;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+--------------------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| BIN_TO_UUID(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">| d9008d47-cdf4-11e8-8d6f-0242ac11001b |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| d900e2b2-cdf4-11e8-8d6f-0242ac11001b |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| d9015ce9-cdf4-11e8-8d6f-0242ac11001b |
&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>You may also find useful the following article - &lt;a href="https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/" target="_blank" rel="noopener noreferrer">Store UUID in an optimized way&lt;/a>.&lt;/p>
&lt;h3 id="using-sequences">Using sequences&lt;/h3>
&lt;p>Some databases support the object type called Sequence that allows generating sequences of numbers. The Oracle MySQL server does not support this object type yet but the MariaDB 10.3 server has the &lt;strong>Sequence&lt;/strong> engine that allows working with the &lt;a href="https://mariadb.com/kb/en/library/sequence-overview/" target="_blank" rel="noopener noreferrer">Sequence&lt;/a> object.&lt;/p>
&lt;p>The Sequence engine provides DDL commands for creating and modifying sequences as well as several auxiliary functions for working with the values. It is possible to specify the following parameters while creating a named sequence: START – a start value, INCREMENT – a step, MINVALUE/MAXVALUE – the minimum and maximum value; CACHE – the size of the cache values; CYCLE/NOCYCLE – the sequence cyclicity. For more information, see the &lt;a href="https://mariadb.com/kb/en/library/create-sequence/" target="_blank" rel="noopener noreferrer">CREATE SEQUENCE documentation&lt;/a>.&lt;/p>
&lt;p>Moreover, the sequence can be used to generate unique numeric values.  This possibility can be considered as an alternative to AUTO_INCREMENT but the sequence additionally provides an opportunity to specify a step of the values. Let’s take a look at this example by using the &lt;code>**users&lt;/code>** table. The sequence object &lt;code>**users_seq&lt;/code>** will be used to fill the values of the primary key. It is enough to specify the &lt;strong>NEXT VALUE FOR&lt;/strong> function in the &lt;strong>DEFAULT&lt;/strong> property of the column:&lt;/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">CREATE SEQUENCE users_seq;
&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 users (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id int NOT NULL DEFAULT (NEXT VALUE FOR users_seq),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> first_name varchar(100) NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> last_name varchar(100) NOT NULL,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> email varchar(254) 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 users (first_name, last_name, email) VALUES ('Simon', 'Wood', 'simon@testhost.com');
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT INTO users (first_name, last_name, email) VALUES ('Peter', 'Hopper', 'peter@testhost.com');&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Table content output:
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/using-sequences-for-pk.png" alt="using sequences for pk generation" />&lt;/figure>&lt;/p>
&lt;h2 id="information">Information&lt;/h2>
&lt;p>The images for this article were produced while using &lt;a href="https://www.devart.com/dbforge/mysql/studio/" target="_blank" rel="noopener noreferrer">dbForge Studio for MySQL Express Edition,&lt;/a> a download is available from &lt;a href="https://www.devart.com/dbforge/mysql/studio/download.html" target="_blank" rel="noopener noreferrer">https://www.devart.com/dbforge/mysql/studio/download.html&lt;/a>&lt;/p>
&lt;h4 id="its-free">It’s free!&lt;/h4>
&lt;p>&lt;strong>Thank you to community reviewer &lt;a href="https://jfg-mysql.blogspot.com/" target="_blank" rel="noopener noreferrer">Jean-François Gagné&lt;/a> for his review and suggestions for this post.&lt;/strong>&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Alexey Mikotkin</author><category>MariaDB</category><category>MySQL</category><category>dbForge</category><category>Entry Level</category><category>GUI tools</category><category>MyISAM</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2018/09/generating-complex-sequences_hu_21437ace800b752a.jpg"/><media:content url="https://percona.community/blog/2018/09/generating-complex-sequences_hu_c70b4fee487cd4e0.jpg" medium="image"/></item><item><title>Deploying MySQL on Kubernetes with a Percona-based Operator</title><link>https://percona.community/blog/2018/10/11/deploying-mysql-on-kubernetes-with-a-percona-based-operator/</link><guid>https://percona.community/blog/2018/10/11/deploying-mysql-on-kubernetes-with-a-percona-based-operator/</guid><pubDate>Thu, 11 Oct 2018 17:03:04 UTC</pubDate><description>In the context of providing managed WordPress hosting services, at Presslabs we operate with lots of small to medium-sized databases, in a DB-per-service model, as we call it. The workloads are mostly reads, so we need to efficiently scale that. The MySQL® asynchronous replication model fits the bill very well, allowing us to scale horizontally from one server—with the obvious availability pitfalls—to tens of nodes. The next release of the stack is going to be open-sourced.</description><content:encoded>&lt;p>In the context of providing managed WordPress hosting services, at &lt;a href="https://www.presslabs.com/" target="_blank" rel="noopener noreferrer">Presslabs&lt;/a> we operate with lots of small to medium-sized databases, in a DB-per-service model, as we call it. The workloads are mostly reads, so we need to efficiently scale that. The MySQL® asynchronous replication model fits the bill very well, allowing us to scale horizontally from one server—with the obvious availability pitfalls—to tens of nodes. The next release of the stack is going to be open-sourced.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/kubernetes-mysql-operator.png" alt="Kubernetes MySQL Operator" />&lt;/figure>&lt;/p>
&lt;p>As we were already using &lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer">Kubernetes&lt;/a>, we were looking for an operator that could automate our DB deployments and auto-scaling. Those available were doing synchronous replication using MySQL group replication or Galera-based replication. Therefore, we decided to write our own operator.&lt;/p>
&lt;h2 id="solution-architecture">Solution architecture&lt;/h2>
&lt;p>The &lt;a href="https://www.presslabs.com/code/mysqloperator/" target="_blank" rel="noopener noreferrer">MySQL operator&lt;/a>, released under Apache 2.0 license, is based on Percona Server for MySQL for its operational improvements —like utility user and backup locks—and relies on the tried and tested &lt;a href="https://github.com/github/orchestrator" target="_blank" rel="noopener noreferrer">Orchestrator&lt;/a> to do the automatic failovers. We’ve been using &lt;a href="https://www.percona.com/software/mysql-database/percona-server" target="_blank" rel="noopener noreferrer">Percona Server&lt;/a> in production for about four years, with very good results, thus encouraging us to continue implementing it in the operator as well.&lt;/p>
&lt;p>The MySQL Operator-Orchestrator integration is highly important for topology, as well as for cluster healing and system failover. Orchestrator is a MySQL high availability and replication management tool that was coded and opened by &lt;a href="https://github.com/" target="_blank" rel="noopener noreferrer">GitHub&lt;/a>.&lt;/p>
&lt;p>As we’re writing this, the operator is undergoing a full rewrite to implement the operator using the &lt;a href="https://github.com/kubernetes-sigs/kubebuilder" target="_blank" rel="noopener noreferrer">Kubebuilder&lt;/a> framework, which is a pretty logical step to simplify and standardize the operator to make it more readable to contributors and users.&lt;/p>
&lt;h2 id="aims-for-the-project">Aims for the project&lt;/h2>
&lt;p>We’ve built the MySQL operator with several considerations in mind, generated by the needs that no other operator could satisfy at the time we started working on it, last year.&lt;/p>
&lt;p>Here are some of them:&lt;/p>
&lt;ul>
&lt;li>Easily deployable MySQL clusters in Kubernetes, following the cluster-per-service model&lt;/li>
&lt;li>DevOps-friendly, critical to basic operations such as monitoring, availability, scalability, and backup stories&lt;/li>
&lt;li>Out-of-the-box backups, scheduled or on-demand, and point-in-time recovery&lt;/li>
&lt;li>Support for cloning, both inside a cluster and across clusters&lt;/li>
&lt;/ul>
&lt;p>It’s good to know that the MySQL operator is now in beta version, and can be tested in production workloads. However, you can take a spin and decide for yourself—we’re already successfully using it for a part of our production workloads at &lt;a href="https://www.presslabs.com/" target="_blank" rel="noopener noreferrer">Presslabs&lt;/a>, for our customer dashboard services.&lt;/p>
&lt;p>Going further to some more practical info, we’ve successfully installed and tested the operator on AWS, Google Cloud Platform, and Microsoft Azure and covered the step by step process in three tutorials here.&lt;/p>
&lt;h2 id="set-up-and-configuration">Set up and configuration&lt;/h2>
&lt;p>It’s fairly simple to use the operator. Prerequisites would be the ubiquitous &lt;a href="https://helm.sh/" target="_blank" rel="noopener noreferrer">Helm&lt;/a> and &lt;a href="https://kubernetes.io/docs/reference/kubectl/overview/" target="_blank" rel="noopener noreferrer">Kubectl&lt;/a>.&lt;/p>
&lt;p>The first step is to install the controller. Two commands should be run, to make use of the Helm chart bundled in the operator:&lt;/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">$ helm repo add presslabs https://presslabs.github.io/charts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ helm install presslabs/mysql-operator --name mysql-operator&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>These commands will deploy the controller together with an Orchestrator cluster. The configuration parameters of the Helm chart for the operator and its default values are as follows:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Parameter&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Default value&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>replicaCount&lt;/code>&lt;/td>
&lt;td>replicas for controller&lt;/td>
&lt;td>&lt;code>1&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>image&lt;/code>&lt;/td>
&lt;td>controller container image&lt;/td>
&lt;td>&lt;code>quay.io/presslabs/mysql-operator:v0.1.5&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>imagePullPolicy&lt;/code>&lt;/td>
&lt;td>controller image pull policy&lt;/td>
&lt;td>&lt;code>IfNotPresent&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>helperImage&lt;/code>&lt;/td>
&lt;td>mysql helper image&lt;/td>
&lt;td>&lt;code>quay.io/presslabs/mysql-helper:v0.1.5&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>installCRDs&lt;/code>&lt;/td>
&lt;td>whether or not to install CRDS&lt;/td>
&lt;td>&lt;code>true&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>resources&lt;/code>&lt;/td>
&lt;td>controller pod resources&lt;/td>
&lt;td>{}&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>nodeSelector&lt;/code>&lt;/td>
&lt;td>controller pod nodeSelector&lt;/td>
&lt;td>{}&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tolerations&lt;/code>&lt;/td>
&lt;td>controller pod tolerations&lt;/td>
&lt;td>{}&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>affinity&lt;/code>&lt;/td>
&lt;td>controller pod affinity&lt;/td>
&lt;td>{}&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>extraArgs&lt;/code>&lt;/td>
&lt;td>args that are passed to controller&lt;/td>
&lt;td>[]&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rbac.create&lt;/code>&lt;/td>
&lt;td>whether or not to create rbac service account, role and roleBinding&lt;/td>
&lt;td>&lt;code>true&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rbac.serviceAccountName&lt;/code>&lt;/td>
&lt;td>If rbac.create is false then this service account is used&lt;/td>
&lt;td>&lt;code>default&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>orchestrator.replicas&lt;/code>&lt;/td>
&lt;td>Control Orchestrator replicas&lt;/td>
&lt;td>&lt;code>3&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>orchestrator.image&lt;/code>&lt;/td>
&lt;td>Orchestrator container image&lt;/td>
&lt;td>&lt;code>quay.io/presslabs/orchestrator:latest&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Further Orchestrator values can be tuned by checking the &lt;a href="https://github.com/presslabs/docker-orchestrator/blob/master/charts/orchestrator/values.yaml" target="_blank" rel="noopener noreferrer">values.yaml&lt;/a> config file.&lt;/p>
&lt;h3 id="cluster-deployment">Cluster deployment&lt;/h3>
&lt;p>The next step is to deploy a cluster. For this, you need to create a Kubernetes secret that contains MySQL credentials (root password, database name, user name, user password), to initialize the cluster and a custom resource MySQL cluster as you can see below:&lt;/p>
&lt;p>An example of a secret (&lt;a href="https://github.com/presslabs/mysql-operator/blob/master/examples/example-cluster-secret.yaml" target="_blank" rel="noopener noreferrer">example-cluster-secret.yaml&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">apiVersion: v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Secret
&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: my-secret
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: Opaque
&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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ROOT_PASSWORD: # root password, base_64 encoded&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>An example of simple cluster (&lt;a href="https://github.com/presslabs/mysql-operator/blob/master/examples/example-cluster.yaml" target="_blank" rel="noopener noreferrer">example-cluster.yaml&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-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: mysql.presslabs.org/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: MysqlCluster
&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: my-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"> replicas: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> secretName: my-secret&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The usual kubectl commands can be used to do various operations, such as a basic listing:&lt;/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 get mysql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>or detailed cluster information:&lt;/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 describe mysql my-cluster&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="backups">Backups&lt;/h3>
&lt;p>A further step could be setting up the backups on an object storage service. To create a backup is as simple as creating a MySQL Backup resource that can be seen in this example (&lt;a href="https://github.com/presslabs/mysql-operator/blob/master/examples/example-backup.yaml" target="_blank" rel="noopener noreferrer">example-backup.yaml&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">apiVersion: mysql.presslabs.org/v1alpha1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: MysqlBackup
&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: my-cluster-backup
&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"> clusterName: my-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"> backupUri: gs://bucket_name/path/to/backup.xtrabackup.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"> backupSecretName: my-cluster-backup-secret&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>To provide credentials for a storage service, you have to create a secret and specify your credentials to your provider; we currently support AWS, GCS or HTTP as in this example (&lt;a href="https://github.com/presslabs/mysql-operator/blob/master/examples/example-backup-secret.yaml" target="_blank" rel="noopener noreferrer">example-backup-secret.yaml&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-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">apiVersion: v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kind: Secret
&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: my-cluster-backup-secret
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">type: Opaque
&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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # AWS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> AWS_ACCESS_KEY_ID: #add here your key, base_64 encoded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> AWS_SECRET_KEY: #and your secret, base_64 encoded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # or Google Cloud base_64 encoded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GCS_SERVICE_ACCOUNT_JSON_KEY: #your key, base_64 encoded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # GCS_PROJECT_ID: #your ID, base_64 encoded&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Also, recurrent cluster backups and cluster initialization from a backup are some additional operations you can opt for. For more details head for our &lt;a href="https://www.presslabs.com/code/mysqloperator/mysql-operator-backups/" target="_blank" rel="noopener noreferrer">documentation page&lt;/a>.&lt;/p>
&lt;p>Further operations and new usage information are kept up-to-date on the project homepage.&lt;/p>
&lt;p>Our future plans include developing the MySQL operator and integrating it with &lt;a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management" target="_blank" rel="noopener noreferrer">Percona Management &amp; Monitoring&lt;/a> for better exposing the internals of the Kubernetes DB cluster.&lt;/p>
&lt;h2 id="open-source-community">Open source community&lt;/h2>
&lt;p>Community contributions are highly appreciated; we should mention the pull requests from &lt;a href="https://platform9.com" target="_blank" rel="noopener noreferrer">Platform9&lt;/a>, so far, but also the sharp questions on the channel we’ve opened on &lt;a href="https://gitter.im/PressLabs/mysql-operator" target="_blank" rel="noopener noreferrer">Gitter&lt;/a>, for which we do the best to answer in detail, as well as issue reports from early users of the operator.&lt;/p>
&lt;h3 id="come-and-talk-to-us-about-the-project">Come and talk to us about the project&lt;/h3>
&lt;p>&lt;a href="https://www.percona.com/live/e18/registration-information" target="_blank" rel="noopener noreferrer">
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/ple18_logo.png" alt="ple18_logo" />&lt;/figure>&lt;/a>Along with my colleague Calin Don, I’ll be talking about this at &lt;a href="https://www.percona.com/live/e18/sessions/automating-mysql-deployments-on-kubernetes" target="_blank" rel="noopener noreferrer">Percona Live Europe&lt;/a> in November. It would be great to have the chance to meet other enthusiasts and talk about what we’ve discovered so far!&lt;/p>
&lt;p>&lt;em>The content in this blog is provided in good faith by members of the open source community. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Flavius Mecea</author><category>Advanced Level</category><category>auto-scaling</category><category>automated deployments</category><category>Containers</category><category>Deployment</category><category>DevOps</category><category>GitHub</category><category>Kubernetes</category><category>MySQL</category><category>Orchestrator</category><category>Percona Server for MySQL</category><category>Scalability</category><media:thumbnail url="https://percona.community/blog/2018/10/kubernetes-mysql-operator_hu_4287e9c6027468e1.jpg"/><media:content url="https://percona.community/blog/2018/10/kubernetes-mysql-operator_hu_7790fd442eb84810.jpg" medium="image"/></item><item><title>Percona Live Europe Tutorial: Elasticsearch 101</title><link>https://percona.community/blog/2018/10/03/percona-live-tutorial-elasticsearch-101/</link><guid>https://percona.community/blog/2018/10/03/percona-live-tutorial-elasticsearch-101/</guid><pubDate>Wed, 03 Oct 2018 07:17:53 UTC</pubDate><description/><content:encoded>&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/elasticsearch-mark.png" alt="Elasticsearch mark" />&lt;/figure>&lt;/p>
&lt;p>For &lt;a href="https://www.percona.com/live/e18/" target="_blank" rel="noopener noreferrer">Percona Live Europe&lt;/a>, I’ll be presenting the tutorial &lt;a href="https://www.percona.com/live/e18/sessions/elasticsearch-101" target="_blank" rel="noopener noreferrer">&lt;em>Elasticsearch 101&lt;/em>&lt;/a> alongside my colleagues and fellow presenters from &lt;a href="https://www.objectrocket.com/" target="_blank" rel="noopener noreferrer">ObjectRocket&lt;/a> &lt;strong>Alex Cercel&lt;/strong>, DBA, and &lt;strong>Mihai Aldoiu&lt;/strong>, Data Engineer. Here’s a brief overview of our tutorial.&lt;/p>
&lt;p>&lt;a href="https://www.elastic.co/" target="_blank" rel="noopener noreferrer">&lt;strong>Elasticsearch®&lt;/strong>&lt;/a> is well known as a highly scalable search engine that stores data in a structure optimized for language based searches but its capabilities and use cases don’t stop there. In this tutorial, we’ll give you a hands-on introduction to Elasticsearch and give you a glimpse at some of the fundamental concepts. We’ll cover various administrative topics like installation and configuration, Cluster/Node management, indexes management and monitoring cluster health. We will also look at developer-oriented topics like mappings and analysis, aggregations and schema design that will help you build a robust application. There will be lab sessions too - bring a laptop!&lt;/p>
&lt;p>&lt;strong>Why’s it exciting?&lt;/strong> Well, although my main focus is on MongoDB, I am a huge fan of polyglot persistence. I start dealing with Elasticsearch, like, a year ago to overcome some MongoDB hard limits, and I must admit I entered a whole new world. Before working with Elasticsearch I was under the misconception “&lt;em>it’s for full-text search only&lt;/em>”. The truth is that the product offers way more than that. I am looking forward to sharing my experience through this presentation.&lt;/p>
&lt;p>Alex and Mihai are senior Elasticsearch data engineers who’ll share their deep knowledge and expertise with the attendees.&lt;/p>
&lt;h2 id="who-would-get-the-most-from-this-talk">Who would get the most from this talk?&lt;/h2>
&lt;p>Well, everyone &lt;em>could&lt;/em> benefit but if I wanted to make it a little bit more specific those with most to gain are:&lt;/p>
&lt;ul>
&lt;li>those who know nothing about Elasticsearch or you fall under the same misconception as I did :)&lt;/li>
&lt;li>someone who wants to start a new project and consider Elasticsearch as an option&lt;/li>
&lt;li>if you are already dealing with Elasticsearch and you want to develop more knowledge of its operations and internals.&lt;/li>
&lt;li>those running Elasticsearch in production and you are facing any type of challenge&lt;/li>
&lt;/ul>
&lt;h2 id="what-presentations-am-i-most-looking-forward-to">What presentations am I most looking forward to?&lt;/h2>
&lt;p>At Percona conferences, I wish I was &lt;a href="https://en.wikipedia.org/wiki/Jamie_Madrox" target="_blank" rel="noopener noreferrer">Jamie Madrox&lt;/a>. I wish I could create “dupes” of myself and attend every presentation. I will try to attend all MongoDB related talks since it’s my primary focus. However, this year I will also watch out for postgres-related talks. Postgres made huge steps forward since the last time I worked with it—it’s become “hot” again in the database world.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/10/Antonios.jpeg" alt="Antonios Giannopoulos in PL tee" />&lt;/figure>
&lt;em>Editor: Thanks to Antonios for modelling a past Percona Live tee. Come join in the tutorial and pick up one of your own.&lt;/em>&lt;/p>
&lt;h2 id="register-now">&lt;a href="https://www.percona.com/live/e18/" target="_blank" rel="noopener noreferrer">Register Now&lt;/a>&lt;/h2>
&lt;p>Percona Live conferences provide the open source database community with an opportunity to discover and discuss the latest open source trends, technologies and innovations. The conference includes the best and brightest innovators and influencers in the open source database industry so don’t delay? &lt;a href="https://www.percona.com/live/e18/" target="_blank" rel="noopener noreferrer">Register now!&lt;/a>&lt;/p></content:encoded><author>Antonios Giannopoulos</author><category>Elasticsearch</category><category>Events</category><category>MariaDB</category><category>MongoDB</category><category>MySQL</category><category>Percona Live Europe 2018</category><category>Tools</category><category>Tutorial</category><media:thumbnail url="https://percona.community/blog/2018/10/MySQL-at-scale_hu_3c5128ac9f54aa12.jpg"/><media:content url="https://percona.community/blog/2018/10/MySQL-at-scale_hu_ec646f6415dc7148.jpg" medium="image"/></item><item><title>Minimize MySQL Deadlocks with 3 Steps</title><link>https://percona.community/blog/2018/09/24/minimize-mysql-deadlocks-3-steps/</link><guid>https://percona.community/blog/2018/09/24/minimize-mysql-deadlocks-3-steps/</guid><pubDate>Mon, 24 Sep 2018 10:49:35 UTC</pubDate><description>MySQL has locking capabilities, for example table and row level locking, and such locks are needed to control data integrity in multi-user concurrency. Deadlocks—where two or more transactions are waiting for one another to give up locks before the transactions can proceed successfully—are an unwanted situation. It is a classic problem for all databases including MySQL/PostgreSQL/Oracle etc. By default, MySQL detects the deadlock condition and to break the deadlock it rolls back one of the transactions.</description><content:encoded>&lt;p>MySQL has locking capabilities, for example table and row level locking, and such locks are needed to control data integrity in multi-user concurrency. Deadlocks—where two or more transactions are waiting for one another to give up locks before the transactions can proceed successfully—are an unwanted situation. It is a classic problem for all databases including MySQL/PostgreSQL/Oracle etc. By default, MySQL detects the deadlock condition and to break the deadlock it rolls back one of the transactions.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/application-deadlock-in-MySQL-transactions.jpg" alt="application deadlock in MySQL transactions" />&lt;/figure>&lt;/p>
&lt;p>For a deadlock example, see &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-example.html" target="_blank" rel="noopener noreferrer">InnoDB deadlocks&lt;/a>&lt;/p>
&lt;h2 id="some-misconceptions">Some misconceptions&lt;/h2>
&lt;p>There are some misconceptions about deadlocks:&lt;/p>
&lt;p>a) &lt;strong>Transaction isolation levels are responsible for deadlocks&lt;/strong>. The possibility of deadlocks is not affected by isolation level. Isolation level changes the behavior of read operations, but deadlock occurs due to write operations. However, isolation level sets fewer locks, hence it can help you to avoid certain lock types (e.g. gap locking).&lt;/p>
&lt;p>b) &lt;strong>Small transactions are not affected by deadlocks.&lt;/strong> Small transactions are less prone to deadlocks but it can still happen if transactions do not use the same order of operations.&lt;/p>
&lt;p>c) &lt;strong>Deadlocks are dangerous.&lt;/strong> I still hear from some customers who are using MyISAM tables that their reason for not switching to InnoDB is the deadlock problem. Deadlocks aren’t dangerous if you retry the transaction that failed due to deadlock and follow the steps given below in this article.&lt;/p>
&lt;p>I hope that this article will help clear such misconceptions.&lt;/p>
&lt;p>Back to the topic of this article. There are many possibilities that can cause deadlocks to occur and, for simplicity, I have grouped my recommendations into 3 steps.&lt;/p>
&lt;h2 id="1-use-a-lock-avoiding-design-strategy">1. Use a lock-avoiding design strategy&lt;/h2>
&lt;ul>
&lt;li>Break big transactions into smaller transactions: keeping transactions short make them less prone to collision.&lt;/li>
&lt;li>If you use INSERT INTO … SELECT to copy some or all rows from one table to another, consider using a lesser locking transaction isolation level (e.g. READ_COMMITTED) and set the binary log format to row/mixed for that transaction. Alternatively, design your application to put a single INSERT statement in a loop and copy row(s) into the table.&lt;/li>
&lt;li>If your application performs locking reads, for example SELECT … FOR UPDATE or SELECT .. FOR SHARE consider using the NOWAIT and SKIPPED LOCK options available in MySQL 8.0, see &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html#innodb-locking-reads-nowait-skip-locked" target="_blank" rel="noopener noreferrer">Locking Read Concurrency with NOWAIT and SKIP LOCKED&lt;/a>. Alternatively, you may consider using a lesser locking transaction isolation level (described earlier)&lt;/li>
&lt;li>Multiple transactions updating data set in one or more tables, should use the same order of operation for their transactions. Avoid locking table A, B, C in one transaction and C,A,B in another.&lt;/li>
&lt;li>If you have the application retry when a transaction fails due to deadlock, you should ideally have the application take a brief pause before resubmitting its query/transaction. This gives the other transaction involved in the deadlock a chance to complete and release the locks that formed part of the deadlock cycle.&lt;/li>
&lt;/ul>
&lt;h2 id="2-optimize-queries">2. Optimize queries&lt;/h2>
&lt;ul>
&lt;li>Well optimized queries examine fewer rows and as result set fewer locks.&lt;/li>
&lt;/ul>
&lt;h2 id="3-disable-deadlock-detection-for-systems-running-mysql-8">3. Disable deadlock detection (for systems running MySQL 8+)&lt;/h2>
&lt;ul>
&lt;li>If you’re running a high concurrency system, it maybe more efficient to disable deadlock detection and rely on the &lt;a href="https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout" target="_blank" rel="noopener noreferrer">innodb_lock_wait_timeout&lt;/a> setting. However, keep this setting low. The default timeout setting is 50 seconds which is too long if you’re running without deadlock detection. Be careful when disabling deadlock detection as it may do more harm than good.&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. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Aftab Khan</author><category>Dev</category><category>Deadlock</category><category>Entry Level</category><category>MySQL</category><media:thumbnail url="https://percona.community/blog/2018/09/application-deadlock-in-MySQL-transactions_hu_ed9ef2c46ac511fb.jpg"/><media:content url="https://percona.community/blog/2018/09/application-deadlock-in-MySQL-transactions_hu_bcc897efd2d2043a.jpg" medium="image"/></item><item><title>Multi-master with MariaDB 10 - a tutorial</title><link>https://percona.community/blog/2018/09/10/multi-master-with-mariadb-10-tutorial/</link><guid>https://percona.community/blog/2018/09/10/multi-master-with-mariadb-10-tutorial/</guid><pubDate>Mon, 10 Sep 2018 13:57:46 UTC</pubDate><description>The goal of this tutorial is to show you how to use multi-master to aggregate databases with the same name, but different data from different masters, on the same slave.</description><content:encoded>&lt;p>The goal of this tutorial is to show you how to use multi-master to aggregate databases with the same name, but different data from different masters, on the same slave.&lt;/p>
&lt;p>Example:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>master1&lt;/strong> => a French subsidiary&lt;/li>
&lt;li>&lt;strong>master2&lt;/strong> => a British subsidiary&lt;/li>
&lt;/ul>
&lt;p>Both have the same database PRODUCTION but the data are totally different.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/pmacli-schema-diagram.jpg" alt="PmaControl schema topology" />&lt;/figure>
&lt;em>This screenshot is made from my own monitoring tool: PmaControl. You have to read 10.10.16.232 on master2 and not 10.10.16.235. The fault of my admin system! :p)&lt;/em>&lt;/p>
&lt;p>We will start with three servers—2 masters and 1 slave—you can add more masters if needed. For this tutorial, I used Ubuntu 12.04. I’ll let you choose the right procedure for your distribution from&lt;a href="https://downloads.mariadb.org/mariadb/" target="_blank" rel="noopener noreferrer">Downloads.&lt;/a>&lt;/p>
&lt;h2 id="scenario">Scenario&lt;/h2>
&lt;ul>
&lt;li>10.10.16.231 : first master (referred to subsequently as master1) => a French subsidiary&lt;/li>
&lt;li>10.10.16.232 : second master (referred to subsequently as master2) => a British subsidiary&lt;/li>
&lt;li>10.10.16.233 : slave (multi-master) (referred to subsequently as slave)&lt;/li>
&lt;/ul>
&lt;p>If you already have your three servers correctly installed, you can scroll down directly to “&lt;em>Dump your master1 and master2 databases from slave&lt;/em>”.&lt;/p>
&lt;h3 id="default-installation-on-3-servers">Default installation on 3 servers&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">apt-get -y install python-software-properties
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">```The main reason I put it in a different file because we use [Chef](https://en.wikipedia.org/wiki/Chef_(software)) as the configuration manager and this overwrites /etc/apt/sources.list . The other reason is that if any trouble occurs, you can just remove this file and restart with the default configuration.```
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">echo "deb http://mirror.stshosting.co.uk/mariadb/repo/10.0/ubuntu precise main" > /etc/apt/sources.list.d/mariadb.list&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">apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get install mariadb-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>The goal of this small script is to get the IP of the server and make a CRC32 from this IP to generate one unique server-id. Generally the command CRC32 isn’t installed, so we will use the one from MySQL. To set account // password we use the account system of Debian / Ubuntu.&lt;/p>
&lt;p>Even if your server has more interfaces, you should have no trouble because the IP address should be unique.&lt;/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=`egrep user /etc/mysql/debian.cnf | tr -d ' ' | cut -d '=' -f 2 | head -n1 | tr -d 'n'`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">passwd=`egrep password /etc/mysql/debian.cnf | tr -d ' ' | cut -d '=' -f 2 | head -n1 | tr -d 'n'`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ip=`ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}' | head -n1 | tr -d 'n'`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crc32=`mysql -u $user -p$passwd -e "SELECT CRC32('$ip')"`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">id_server=`echo -n $crc32 | cut -d ' ' -f 2 | tr -d 'n'`&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This configuration file is not one I use in production, but a minimal version that’s shown just as an example. The config may work fine for me, but perhaps it won’t be the same for you, and it might just crash your MySQL server.&lt;/p>
&lt;p>If you’re interested in my default installof MariaDB 10 you can see it here: &lt;a href="https://raw.githubusercontent.com/Esysteme/Debian/master/mariadb.sh" target="_blank" rel="noopener noreferrer">https://raw.githubusercontent.com/Esysteme/Debian/master/mariadb.sh&lt;/a> (this script as been updated since 4 years)&lt;/p>
&lt;p>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-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">./mariadb.sh -p 'secret_password' -v 10.3 -d /src/mysql&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">cat >> /etc/mysql/conf.d/mariadb10.cnf &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">[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"># default-character-set = utf8
&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-client-handshake = FALSE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">character-set-server = utf8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">collation-server = utf8_general_ci
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&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">external-locking = off
&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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#make a crc32 of ip server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">server-id=$id_server
&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 prevent auto start of thread slave
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">skip-slave-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">[mysql]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">default-character-set = utf8
&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>We restart 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-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">/etc/init.d/mysql restart&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"> * Stopping MariaDB database server mysqld [ OK ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * Starting MariaDB database server mysqld [ OK ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * Checking for corrupt, not cleanly closed and upgrade needing tables.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Repeat these actions on all three servers.&lt;/p>
&lt;h2 id="create-users-on-both-masters">Create users on both masters&lt;/h2>
&lt;h3 id="create-the-replication-user-on-both-masters">Create the replication user on both masters&lt;/h3>
&lt;p>on &lt;strong>master1&lt;/strong> (10.10.16.231)&lt;/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 -u root -p -e "GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replication'@'%' IDENTIFIED BY 'passwd';"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>on &lt;strong>master2&lt;/strong> (10.10.16.232)&lt;/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">mysql -u root -p -e "GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replication'@'%' IDENTIFIED BY 'passwd';"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="create-a-user-for-external-backup">Create a user for external backup&lt;/h3>
&lt;p>On master1 and on master2&lt;/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">mysql -u root -p -e "GRANT SELECT, LOCK TABLES, RELOAD, REPLICATION CLIENT, SUPER ON *.* TO 'backup'@'10.10.16.%' IDENTIFIED BY 'passwd' WITH GRANT OPTION;"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="if-you-are-just-testing">If you are just testing…&lt;/h2>
&lt;p>If you don’t have a such a configuration and you want to set up tests:&lt;/p>
&lt;h3 id="create-a-database-on-master1-101016231">Create a database on master1 (10.10.16.231)&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-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">master1 [(NONE)]> CREATE DATABASE PRODUCTION;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="create-a-database-on-master2-101016232">Create a database on master2 (10.10.16.232)&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-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">master2 [(NONE)]> CREATE DATABASE PRODUCTION;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="dump-your-master1-and-master2-databases-from-slave-101016233">Dump your master1 and master2 databases from slave (10.10.16.233)&lt;/h2>
&lt;p>All the commands from now until the end have to be carried out on the &lt;strong>slave&lt;/strong> server&lt;/p>
&lt;ul>
&lt;li>–master-data=2 get the file (binary log) and its position, and add it to the beginning of the dump as a comment&lt;/li>
&lt;li>–single-transaction This option issues a BEGIN SQL statement before dumping data from the server (this works only on tables with the InnoDB storage engine)&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-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">mysqldump -h 10.10.16.231 -u root -p --master-data=2 --single-transaction PRODUCTION > PRODUCTION_10.10.16.231.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysqldump -h 10.10.16.232 -u root -p --master-data=2 --single-transaction PRODUCTION > PRODUCTION_10.10.16.232.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Create both new 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-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">slave[(NONE)]> CREATE DATABASE PRODUCTION_FR;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave[(NONE)]> CREATE DATABASE PRODUCTION_UK;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Load the 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">mysql -h 10.10.16.233 -u root -p PRODUCTION_FR &lt; PRODUCTION_10.10.16.231.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql -h 10.10.16.233 -u root -p PRODUCTION_UK &lt; PRODUCTION_10.10.16.232.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="set-up-both-replications-on-the-slave">Set up both replications on the slave&lt;/h2>
&lt;p>Edit both dumps to get file name and position of the binlog, and replace it here: (use the command “less” instead of other commands in huge files)&lt;/p>
&lt;h3 id="french-subsidiary--master1">French subsidiary – master1&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-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">less PRODUCTION_10.10.16.231.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>get the line : (the MASTER_LOG_FILE and MASTER_LOG_POS values will be different to this 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-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">-- CHANGE MASTER TO MASTER_LOG_FILE='mariadb-bin.000010', MASTER_LOG_POS=771;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>replace the file and position in 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-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">CHANGE MASTER 'PRODUCTION_FR' TO MASTER_HOST = "10.10.16.231", MASTER_USER = "replication", MASTER_PASSWORD ="passwd", MASTER_LOG_FILE='mariadb-bin.000010', MASTER_LOG_POS=771;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="english-subsidiary--master2">English subsidiary – master2&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-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">less PRODUCTION_10.10.16.232.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>get the line: (the MASTER_LOG_FILE and MASTER_LOG_POS values will be different to this example, and would normally be different between master1 and master2. It’s just in my test example they were the same)&lt;/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">-- CHANGE MASTER TO MASTER_LOG_FILE='mariadb-bin.000010', MASTER_LOG_POS=771;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>replace the file and position in 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-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">CHANGE MASTER 'PRODUCTION_UK' TO MASTER_HOST = "10.10.16.232", MASTER_USER = "replication", MASTER_PASSWORD ="passwd", MASTER_LOG_FILE='mariadb-bin.000010', MASTER_LOG_POS=771;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h3 id="rules-of-replication-on-config-file">Rules of replication on config file&lt;/h3>
&lt;p>Unfortunately, the option replicate-rewrite-db doesn’t exist for variables, and we cannot set up this kind of configuration without restarting the slave server. In the section relating to the slave, add the following lines 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-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">/etc/mysql/my.cnf&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>add these lines :&lt;/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">PRODUCTION_FR.replicate-rewrite-db="PRODUCTION->PRODUCTION_FR"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PRODUCTION_UK.replicate-rewrite-db="PRODUCTION->PRODUCTION_UK"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PRODUCTION_FR.replicate-do-db="PRODUCTION_FR"
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PRODUCTION_UK.replicate-do-db="PRODUCTION_UK"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>After that, you can restart the daemon without a problem – but don’t forgot to launch the slaves because we skipped that at the start ;).&lt;/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">/etc/init.d/mysql restart&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Start the replication:&lt;/p>
&lt;ul>
&lt;li>one by one&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-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">START SLAVE 'PRODUCTION_FR';
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">START SLAVE 'PRODUCTION_UK';&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;ul>
&lt;li>all at the same time:&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-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">START ALL SLAVES;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now to check the replication:&lt;/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">slave[(NONE)]>SHOW SLAVE 'PRODUCTION_UK' STATUS;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave[(NONE)]>SHOW SLAVE 'PRODUCTION_FR' STATUS;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave[(NONE)]>SHOW ALL SLAVES STATUS;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="tests">Tests&lt;/h2>
&lt;p>on &lt;strong>slave&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-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">slave [(NONE)]> USE PRODUCTION_FR;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [PRODUCTION_FR]> SHOW TABLES;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Empty 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">slave [(NONE)]> USE PRODUCTION_UK;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [PRODUCTION_UK]> SHOW TABLES;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Empty SET (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>on &lt;strong>master1&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-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">master1 [(NONE)]> USE PRODUCTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">master1 [PRODUCTION]>CREATE TABLE `france` (id INT);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 ROWS affected (0.13 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">master1 [PRODUCTION]> INSERT INTO `france` SET id=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;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>on &lt;strong>master2&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-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">master2 [(NONE)]> USE PRODUCTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">master2 [PRODUCTION]>CREATE TABLE `british` (id INT);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 0 ROWS affected (0.13 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">master2 [PRODUCTION]> INSERT INTO `british` SET id=2;
&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;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>on &lt;strong>slave&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-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 FRANCE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [(NONE)]> USE PRODUCTION_FR;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [PRODUCTION_FR]> SHOW TABLES;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Tables_in_PRODUCTION_FR |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| france |
&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">slave [PRODUCTION_FR]> SELECT * FROM france;
&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>&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;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">-- for British
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [(NONE)]> USE PRODUCTION_UK;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">slave [PRODUCTION_UK]> SHOW TABLES;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Tables_in_PRODUCTION_UK |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+-------------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| british |
&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">slave [PRODUCTION_UK]> SELECT * FROM british;
&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>&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">1 ROW IN SET (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>It works!&lt;/p>
&lt;p>If you want do this online, please add +1 to: &lt;a href="https://jira.mariadb.org/browse/MDEV-17165" target="_blank" rel="noopener noreferrer">https://jira.mariadb.org/browse/MDEV-17165&lt;/a>&lt;/p>
&lt;h2 id="limitations">Limitations&lt;/h2>
&lt;h4 id="warning-it-doesnt-work-with-the-database-specified-in-query-with-binlog_format--statement-or-mixed">&lt;strong>WARNING&lt;/strong>: it doesn’t work with the database specified in query. (With Binlog_format = STATEMENT or MIXED)&lt;/h4>
&lt;p>This works fine:&lt;/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">USE PRODUCTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">UPDATE `ma_table` SET id=1 WHERE id =2;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>This query will break the replication:&lt;/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">USE PRODUCTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">UPDATE `PRODUCTION`.`ma_table` SET id=1 WHERE id =2;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>=> databases &lt;code>PRODUCTION&lt;/code> does not exist on this server.&lt;/p>
&lt;h3 id="a-real-example">A real example&lt;/h3>
&lt;h4 id="missing-update">Missing update&lt;/h4>
&lt;p>on &lt;strong>master1:&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-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">master1 [(NONE)]>UPDATE `PRODUCTION`.`france` SET id=3 WHERE id =1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query OK, 1 ROW affected (0.02 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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">master1 [(NONE)]> SELECT * FROM `PRODUCTION`.`france`;
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">| 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">1 ROW IN SET (0.00 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>on &lt;strong>slave:&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-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">slave [PRODUCTION_FR]> SELECT * FROM france;
&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>&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>In this case we missed the update. It’s a real problem, because if the replication should crash, our slave is desynchronized with master1 and we didn’t realize it.&lt;/p>
&lt;h4 id="crash-replication">Crash replication&lt;/h4>
&lt;p>on &lt;strong>master1&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-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">master1[(NONE)]> USE PRODUCTION;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DATABASE changed
&lt;/span>&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">master1 [PRODUCTION]> SELECT * FROM`PRODUCTION`.`france`;
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">| 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">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">master1 [PRODUCTION]>UPDATE `PRODUCTION`.`france` SET id=4 WHERE id =3;
&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;span class="line">&lt;span class="cl">ROWS matched: 1 Changed: 1 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">master1 [PRODUCTION]> SELECT * FROM `PRODUCTION`.`france`;
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">| 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">1 ROW IN SET (0.01 sec)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>&lt;em>on PmaControl:&lt;/em>
&lt;figure>&lt;img src="https://percona.community/blog/2018/09/pmacli-schema-diagram-1.jpg" alt="pmacli schema diagram showing error" />&lt;/figure> on &lt;strong>slave:&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-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">slave [PRODUCTION_FR]> SHOW slave 'PRODUCTION_FR' STATUSG;
&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"> Slave_IO_State: Waiting FOR master TO send event
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_Host: 10.10.16.231
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_User: replication
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_Port: 3306
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Connect_Retry: 60
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_Log_File: mariadb-bin.000010
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Read_Master_Log_Pos: 2737
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Relay_Log_File: mysqld-relay-bin-production_fr.000003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Relay_Log_Pos: 2320
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Relay_Master_Log_File: mariadb-bin.000010
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Slave_IO_Running: Yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Slave_SQL_Running: No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Do_DB: PRODUCTION_FR
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Ignore_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">Replicate_Do_Table:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Ignore_Table:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Wild_Do_Table:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Wild_Ignore_Table:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_Errno: 1146
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_Error: Error 'Table 'PRODUCTION.france' doesn't exist' on query. Default database: 'PRODUCTION_FR'. Query: 'UPDATE `PRODUCTION`.`france` SET id=4 WHERE id =3'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Skip_Counter: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Exec_Master_Log_Pos: 2554
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Relay_Log_Space: 2815
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Until_Condition: None
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Until_Log_File:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Until_Log_Pos: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Allowed: No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_CA_File:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_CA_Path:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Cert:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Cipher:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Key:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Seconds_Behind_Master: NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Master_SSL_Verify_Server_Cert: No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_IO_Errno: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_IO_Error:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_SQL_Errno: 1146
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last_SQL_Error: Error 'TABLE 'PRODUCTION.france' doesn't exist' ON query. DEFAULT DATABASE: 'PRODUCTION_FR'. Query: 'UPDATE `PRODUCTION`.`france` SET id=4 WHERE id =3'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replicate_Ignore_Server_Ids:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_Server_Id: 2370966657
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Crl:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Master_SSL_Crlpath:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Using_Gtid: No
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Gtid_IO_Pos:
&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">ERROR: No query specified&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And we got the error which crash replication : Error TABLE ‘PRODUCTION.france’ doesn’t exist’ ON query. DEFAULT DATABASE: ‘PRODUCTION_FR’. Query: ‘UPDATE &lt;code>PRODUCTION&lt;/code>.&lt;code>france&lt;/code> SET id=4 WHERE id =3&lt;/p>
&lt;p>NB : Everything works fine with binlog_format=ROW.&lt;/p>
&lt;p>&lt;strong>Author:&lt;/strong> Aurélien LEQUOY &lt;aurelien.lequoy＠esysteme.com> you don’t copy/paste the email, it won’t work. You didn’t think I would post it like that in the open for all bots, right? ;).&lt;/p>
&lt;h2 id="license">License&lt;/h2>
&lt;p>This article is published under: The GNU General Public License v3.0 &lt;a href="http://opensource.org/licenses/GPL-3.0" target="_blank" rel="noopener noreferrer">http://opensource.org/licenses/GPL-3.0&lt;/a>&lt;/p>
&lt;h2 id="others">Others&lt;/h2>
&lt;p>The point of interest is to describe a real use case with full technical information to allow you to reproduce it by yourself. This article was originally published just after the release of MariaDB 10.0 on the now defunct website &lt;a href="https://www.mysqlplus.net" target="_blank" rel="noopener noreferrer">www.mysqlplus.net&lt;/a>.&lt;/p></content:encoded><author>Aurélien LEQUOY</author><category>MariaDB</category><category>MySQL</category><category>Open Source Databases</category><category>Replication</category><media:thumbnail url="https://percona.community/blog/2018/09/pmacli-schema-diagram_hu_2e2b17a4625d1e6f.jpg"/><media:content url="https://percona.community/blog/2018/09/pmacli-schema-diagram_hu_5b37391faddafa9e.jpg" medium="image"/></item><item><title>7 Checks to Successfully Upgrade MongoDB Replica Set in Production</title><link>https://percona.community/blog/2018/08/29/7-checks-successfully-upgrade-mongodb-replica-set-production/</link><guid>https://percona.community/blog/2018/08/29/7-checks-successfully-upgrade-mongodb-replica-set-production/</guid><pubDate>Wed, 29 Aug 2018 10:23:20 UTC</pubDate><description>MongoDB ships powerful features in each release. The new release brings new features while revisions add bug fixes, security patches or improvements to existing features. To bring most out these releases to your plate you should always consider upgrading your MongoDB deployments.</description><content:encoded>&lt;p>MongoDB ships powerful features in each release. The new release brings new features while revisions add bug fixes, security patches or improvements to existing features. To bring most out these &lt;a href="https://docs.mongodb.com/manual/release-notes/3.6/" target="_blank" rel="noopener noreferrer">releases&lt;/a> to your plate you should always consider upgrading your MongoDB deployments.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/checklist-for-the-upgrade-of-MongoDB-replica-set.jpg" alt="checklist for the upgrade of MongoDB replica set" />&lt;/figure>&lt;/p>
&lt;p>Planning your database upgrade can avoid the nightmares caused due to &lt;em>database-upgrade-gone-wrong&lt;/em> or avoid &lt;em>not-so-simple&lt;/em> rollbacks in your production database. Grab a cup of coffee and sit back. This blog post explains the few important to have items on your checklist to plan MongoDB replica set upgrades.&lt;/p>
&lt;h2 id="1-data-compatibility-first">1. Data Compatibility First&lt;/h2>
&lt;p>It is important to identify the data compatibility between your current MongoDB version and the version planned to upgrade. MongoDB sometimes introduces changes to configurations, metadata, protocol version, &lt;a href="https://docs.mongodb.com/manual/release-notes/3.4-compatibility/#stricter-validation-of-collection-and-index-specifications" target="_blank" rel="noopener noreferrer">validations&lt;/a>, &lt;a href="https://docs.mongodb.com/manual/reference/limits/#indexes" target="_blank" rel="noopener noreferrer">indexes &lt;/a>or options. The best way to identify such differences is to go through release specific compatibility &lt;a href="https://docs.mongodb.com/manual/release-notes/3.4-compatibility/#stricter-validation-of-collection-and-index-specifications" target="_blank" rel="noopener noreferrer">changes&lt;/a> and measure the impact.&lt;/p>
&lt;h2 id="2-is-your-driver-compatible">2. Is your Driver Compatible?&lt;/h2>
&lt;p>The driver compatibility &lt;a href="https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#node-js-driver-compatibility" target="_blank" rel="noopener noreferrer">matrix&lt;/a> lists the versions of MongoDB and language-specific versions that are compatible with those versions. The newer version of MongoDB can introduce the changes that affect compatibility with the older version. Familiarise yourself with release specific driver compatibility changes and implement what matters.&lt;/p>
&lt;h2 id="3-follow-upgrade-path">3. Follow Upgrade Path&lt;/h2>
&lt;p>To upgrade the newer version of MongoDB you must have already upgraded to the previous major version release series. For example, if you want to upgrade to version 4.0, you must have already upgraded to version 3.6. If you’re running version 3.4 and planning to upgrade to version 4.0, you must upgrade MongoDB to stable release series 3.6.&lt;/p>
&lt;p>Wait, there’s something more that can change the upgrade plan: if you wish to consider the possibility of a downgrade, it is recommended that you downgrade to the latest revision of the version you would want to downgrade to. This may prompt you to change your upgrade plan in this order:&lt;/p>
&lt;ul>
&lt;li>Upgrade your current MongoDB version to the latest revision of current release series&lt;/li>
&lt;li>Go to check 1 and plan your upgrade&lt;/li>
&lt;/ul>
&lt;h2 id="4-feature-compatibility-flags">4. Feature Compatibility Flags&lt;/h2>
&lt;p>Starting from version 3.4, MongoDB introduced feature compatibility flags(&lt;em>some serious stuff goes here)&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://docs.mongodb.com/manual/reference/command/setFeatureCompatibilityVersion/#dbcmd.setFeatureCompatibilityVersion" target="_blank" rel="noopener noreferrer">setFeatureCompatibilityVersion&lt;/a> allows you to set the features those are &lt;em>incompatible&lt;/em> with the previous versions ON or OFF.&lt;/p>
&lt;p>For example, MongoDB 3.4 introduced backward-incompatible features such as Views, Decimal Type, Collation and Case-Insensitive Indexes. To enable these features in MongoDB 3.4 you must set the FeatureCompatibilityVersion to 3.4 while upgrading from version 3.2. To upgrade to a newer version of MongoDB, you must have the feature compatibility flag set as previous release series.&lt;/p>
&lt;h2 id="5-rehearse-the-upgrade-in-non-production">5. Rehearse the Upgrade in Non-Production&lt;/h2>
&lt;p>Now that you’re prepared, you can upgrade the MongoDB replica set in rolling fashion. This upgrade will involve the DB upgrade, driver upgrades and application code that is compatible with this driver version and DB version. To minimize the impact, upgrade secondaries in a replica set first, followed by stepping down a primary and its upgrade.&lt;/p>
&lt;p>&lt;em>Test your downgrade path:&lt;/em> Prepare for downgrading in the test environment. A MongoDB replica set must follow the downgrade path from the path to be upgraded to, to the latest revision of the currently used release series.&lt;/p>
&lt;h2 id="6-allow-a-burn-in-period">6. Allow a Burn-in Period&lt;/h2>
&lt;p>You need to enable compatibility flags after the replica set upgrade. But let it take some time. Once you’ve verified that everything is all set and there is little likelihood of needing to downgrade, you can set the feature compatibility flags as mentioned in check 4.&lt;/p>
&lt;h2 id="7-upgrade-mongodb-tools">7. Upgrade MongoDB Tools&lt;/h2>
&lt;p>Once you’ve successfully upgraded to a newer version, upgrade the mongodb tools used to connect your deployment.&lt;/p>
&lt;ul>
&lt;li>Upgrade the mongo shell to the same version as the MongoDB deployment&lt;/li>
&lt;li>Upgrade mongodump and mongorestore versions used in your backup and restore scripts. Use the same version of mongodump/mongorestore to backup/restore deployment of the same version on MongoDB.&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. The content is not edited or tested by Percona, and views expressed are the authors’ own. When using the advice from this or any other online resource &lt;strong>test&lt;/strong> ideas before applying them to your production systems, and **always **secure a working back up.&lt;/em>&lt;/p></content:encoded><author>Atish A</author><category>MongoDB</category><category>Open Source Databases</category><category>Replication</category><category>Upgrade</category><media:thumbnail url="https://percona.community/blog/2018/08/checklist-for-the-upgrade-of-MongoDB-replica-set_hu_363944120499aeb7.jpg"/><media:content url="https://percona.community/blog/2018/08/checklist-for-the-upgrade-of-MongoDB-replica-set_hu_538719a5048f4234.jpg" medium="image"/></item><item><title>Question about Semi-Synchronous Replication: the Answer with All the Details</title><link>https://percona.community/blog/2018/08/23/question-about-semi-synchronous-replication-answer-with-all-the-details/</link><guid>https://percona.community/blog/2018/08/23/question-about-semi-synchronous-replication-answer-with-all-the-details/</guid><pubDate>Thu, 23 Aug 2018 12:49:59 UTC</pubDate><description>I was recently asked a question by mail about MySQL Lossless Semi-Synchronous Replication. As I think the answer could benefit many people, I am answering it in a blog post. The answer brings us to the internals of transaction committing, of semi-synchronous replication, of MySQL (server) crash recovery, and of storage engine (InnoDB) crash recovery. I am also debunking some misconceptions that I have often seen and heard repeated by many. Let’s start by stating one of those misconceptions.</description><content:encoded>&lt;p>I was recently asked a question by mail about &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html" target="_blank" rel="noopener noreferrer">MySQL Lossless Semi-Synchronous Replication&lt;/a>. As I think the answer could benefit many people, I am answering it in a blog post. The answer brings us to the internals of transaction committing, of semi-synchronous replication, of MySQL (server) crash recovery, and of storage engine (InnoDB) crash recovery. I am also debunking some misconceptions that I have often seen and heard repeated by many. Let’s start by stating one of those misconceptions.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/semi-sync-replication-MySQL.jpg" alt="semi-sync replication MySQL" />&lt;/figure>&lt;/p>
&lt;p>One of those misconceptions is the following (this is NOT true): semi-synchronous enabled slaves are always the most up-to-date slaves (again, this is &lt;strong>NOT&lt;/strong> true). If you hear it yourself, then please call people out on it to avoid this spreading more. Even if some slaves have semi-synchronous replication disabled (I will use semi-sync for short in the rest of this post), these could still be the most up-to-date slaves after a master crash. I guess this false idea is coming from the name of the feature, not much can be done about this anymore (naming is hard). The details are in the rest of this post.&lt;/p>
&lt;p>Back to the question I received by mail, it can be summarized as follows:&lt;/p>
&lt;ul>
&lt;li>In a deployment where a MySQL 5.7 master is crashed (kill -9 or echo c > /proc/sysrq-trigger ), a slave is promoted as the new master;&lt;/li>
&lt;li>when the old master is brought back up, transactions that are not on the new master are observed on this old master;&lt;/li>
&lt;li>is this normal in a lossless semi-sync environment?&lt;/li>
&lt;/ul>
&lt;p>The answer to that question is yes: it is normal to have transactions on the recovered old master that are not on the new master. This is not a violation of the semi-sync promise. To understand this, we need to go in detail about semi-sync (MySQL 5.5 and 5.6) and lossless semi-sync (MySQL 5.7).&lt;/p>
&lt;h2 id="semi-sync-and-lossless-semi-sync">Semi-Sync and Lossless Semi-Sync&lt;/h2>
&lt;p>&lt;a href="https://dev.mysql.com/doc/refman/5.5/en/replication-semisync.html" target="_blank" rel="noopener noreferrer">Semi-sync replication&lt;/a> was introduced in MySQL 5.5. Its promise is that every transaction where the client has received a COMMIT acknowledgment would be replicated to a slave. It had a caveat though: while a client is waiting for this COMMIT acknowledgment, other clients could see the data of the committing transaction. If the master crashes at this moment (without a slave having received the transaction), it is a violation of transaction isolation. This is also known as phantom read: data observed by a client has disappeared. This is not very satisfactory.&lt;/p>
&lt;p>&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html" target="_blank" rel="noopener noreferrer">Lossless semi-sync replication&lt;/a> was introduced in MySQL 5.7 to solve this problem. With lossless semi-sync, we keep the promise of semi-sync (every transaction where clients have received a COMMIT acknowledgment is replicated), with the additional promise that there is no phantom reads. To understand how this works, we need to dive into the way MySQL commits transactions.&lt;/p>
&lt;h2 id="the-way-mysql-commits-transactions">The Way MySQL Commits Transactions&lt;/h2>
&lt;p>When MySQL commits a transaction, it is going through the following steps:&lt;/p>
&lt;ol>
&lt;li>&lt;em>Prepare&lt;/em> the transaction in the storage engine (InnoDB),&lt;/li>
&lt;li>Write the transaction to the binary logs,&lt;/li>
&lt;li>&lt;em>Complete&lt;/em> the transaction in the storage engine,&lt;/li>
&lt;li>Return an acknowledgment to the client.&lt;/li>
&lt;/ol>
&lt;p>The implementation of semi-sync or lossless semi-sync inserts themselves into the above process.&lt;/p>
&lt;p>Semi-sync in MySQL 5.5 and 5.6 happens between step #3 and #4. After “completing” the transaction in the storage engine, a semi-sync master waits for one slave to confirm the replication of the transaction. As this happens after the storage engine has “completed” the transaction, other clients can see this transaction. &lt;strong>This is the cause of phantom reads.&lt;/strong> Also — unrelated to phantom reads — if the master crashes at that moment and after bringing it back up, this transaction will be in the database as it has been fully “completed” in the storage engine.&lt;/p>
&lt;p>It is important to realize that for semi-sync (and lossless-semi-sync), transactions are written to the binary logs in the same way as in standard (non-semi-sync) replication. In other words, standard and semi-sync replication behave exactly the same way up to and including step #2. Also, once transactions are in the binary logs, they are visible to all slaves, not only to the semi-sync slaves. So a non-semi-sync slave could receive a transaction before the semi-sync slaves. This is why it is false to assume that the semi-sync slaves are the most up-to-date slaves after a master crash.&lt;/p>
&lt;h4 id="it-is-false-to-assume-that-the-semi-sync-slaves-are-the-most-up-to-date-slaves-after-a-master-crash">It is false to assume that the semi-sync slaves are the most up-to-date slaves after a master crash.&lt;/h4>
&lt;p>In lossless semi-sync, waiting for transaction replication happens between steps #2 and #3. At this point, the transaction is not “completed” in the storage engine, so other clients do not see its data yet. But even if this transaction is not “completed”, a master crash at that moment and a subsequent restart would cause this transaction to be in the database. To understand why, we need to dive into MySQL and InnoDB crash recovery.&lt;/p>
&lt;h2 id="mysql-and-innodb-crash-recovery">MySQL and InnoDB Crash Recovery&lt;/h2>
&lt;p>During InnoDB crash recovery, transactions that are not “completed” (have not reached step #3 of transaction committing) are rolled back. So a transaction that is not yet committed (has not reached step #1) or a transaction that is not yet written to the binary logs (has not reached step #2) will not be in the database after InnoDB crash recovery. However, if InnoDB rolled back a transaction that has reached the binary logs (step #2) but that is not “completed” (step #3), this would mean a transaction that could have reached a slave would disappear from the master. This would create data inconsistency in replication and would be bad.&lt;/p>
&lt;h4 id="once-a-transaction-reaches-the-binary-logs-it-should-roll-forward">Once a transaction reaches the binary logs it should roll forward.&lt;/h4>
&lt;p>To avoid the data inconsistency described above, MySQL does its own crash recovery before storage engine crash recovery. This recovery consists of making sure that all the transactions in the binary logs are flagged as “completed”. So if a transaction is between step #2 and #3 at the time of the crash, it is flagged as “completed” in the storage engine during MySQL crash recovery and it is rolled forward during storage engine crash recovery. In the case where this transaction has not reached at least a slave at the moment of the crash, it will appear in the master after crash recovery. It is important to note that this could happen even without semi-sync.&lt;/p>
&lt;h4 id="having-extra-transactions-on-a-recovered-master-can-happen-even-without-semi-sync">Having extra transactions on a recovered master can happen even without semi-sync.&lt;/h4>
&lt;p>The extra transactions that are visible on the recovered old master are because of the way MySQL and InnoDB carry out crash recovery. This is more likely to happen in a lossless semi-sync environment because of the delay introduced between steps #2 and #3 of the way MySQL commits transactions, but it could also happen without semi-sync if the timing is right.&lt;/p>
&lt;h2 id="the-facebook-trick-to-avoid-extra-transactions">The Facebook Trick to Avoid Extra Transactions&lt;/h2>
&lt;p>There is an original trick to avoid having extra transactions on a recovered master. This trick was presented by Facebook during a talk at &lt;a href="https://www.percona.com/live/" target="_blank" rel="noopener noreferrer">Percona Live&lt;/a> a few years ago (sorry, I cannot find any link to this, please post a comment below if you know of public content about this). The idea is to force MySQL to roll-back (instead of rolling forward) the transactions that are not yet “completed” in the storage engine. It must be noted that this should only be done on an old master that has been replaced by a slave. If it is done on a recovering master without failing over to a slave, a transaction that could have reached a slave would disappear from the master.&lt;/p>
&lt;p>To trick MySQL into rolling back the non “completed” transactions, Facebook truncates the binary logs before restarting the old master. This way, MySQL thinks that the crash happened before writing to the binary logs (step #2). So MySQL crash recovery will not flag the transactions as “complete” in the storage engine and these will be rolled back during storage engine crash recovery. This avoids the recovered old master having extra transactions. Obviously, because these transactions were once in the binary logs, they could have been replicated to slaves. So the Facebook trick avoids the old master being ahead of the new master, possibly at the cost of bringing the old master behind the new master.&lt;/p>
&lt;p>I know that Facebook then re-slaves the recovered old master to the new master, but I am not sure that this is possible with standard MySQL. The Facebook variant of MySQL includes additional features, and I think one of those is to put GTIDs in the InnoDB Redo logs. With this, and after the recovery of the old master, the GTID state of the database can be determined even if the binary logs are gone. In standard MySQL, I think that truncating the binary logs will result in losing the GTID state of the database, which will prevent re-slaving the old master to the new master. However, as InnoDB crash recovery prints the binary log position or the last committed transaction, I think re-slaving the old master to a &lt;a href="https://medium.com/booking-com-infrastructure/abstracting-binlog-servers-and-mysql-master-promotion-without-reconfiguring-all-slaves-44be1febc8a0" target="_blank" rel="noopener noreferrer">Binlog Server&lt;/a> would be possible in a semi-sync environment.&lt;/p>
&lt;p>You can read more about semi-synchronous replication at Facebook below:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://yoshinorimatsunobu.blogspot.com/2014/04/semi-synchronous-replication-at-facebook.html" target="_blank" rel="noopener noreferrer">Semi-Synchronous Replication at Facebook&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/live/data-performance-conference-2016/sessions/highs-and-lows-semi-synchronous-replication" target="_blank" rel="noopener noreferrer">The highs and lows of semi-synchronous replication&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="debunking-other-misconceptions">Debunking Other Misconceptions&lt;/h2>
&lt;p>Before closing this post, I would like to debunk other misconceptions that I often hear. Some people say that semi-sync (or lossless semi-sync) increases the availability of MySQL. In my humble opinion, &lt;strong>this is false.&lt;/strong> Semi-sync and lossless semi-sync actually lower availability, there is no increase here.&lt;/p>
&lt;h4 id="lossless-semi-sync-is-not-a-high-availability-solution">Lossless semi-sync is not a high availability solution.&lt;/h4>
&lt;p>The statement that semi-sync and lossless semi-sync have lower availability than standard replication is justified by the introduction of new situations where transactions could be prevented from committing. As an example, if no semi-sync slaves are present, transactions will not be able to commit. The promise of lossless semi-sync is not about increasing availability, it is about preventing the loss of committed transactions in case of a crash. The cost of this promise is the added COMMIT latency and the new cases where COMMIT would be prevented from succeeding (thus reducing availability).&lt;/p>
&lt;h4 id="group-replication-is-not-a-high-availability-solution">Group Replication is not a high availability solution.&lt;/h4>
&lt;p>For the same reasons, Group Replication (or Galera or Percona XtraDB Cluster) reduces availability. Group replication also brings the promise of preventing the loss of committed transactions at the cost of adding COMMIT latency. There is also another cost of Group Replication: failing COMMIT in some situations (I do not know of any situation in standard MySQL where COMMIT can fail, if you know of one, please post a comment below). An example of COMMIT failing is mentioned in my previous post on &lt;a href="http://jfg-mysql.blogspot.com/2018/01/more-write-set-in-mysql-5-7-group-replication-certification.html" target="_blank" rel="noopener noreferrer">Group Replication certification&lt;/a>. This additional cost introduces another interesting promise, but as this is not a post on Group Replication, so I am not covering this here.&lt;/p>
&lt;h4 id="group-replication-also-introduces-cases-where-commit-can-fail">Group Replication also introduces cases where COMMIT can fail.&lt;/h4>
&lt;p>This does not mean that lossless semi-sync and Group Replication cannot be used as a building block for a high availability solution, but by themselves and without other important components, they are not a high availability solution.&lt;/p>
&lt;h2 id="thoughts-about-rpl_semi_sync_master_timeoutwait_no_slave">Thoughts about rpl_semi_sync_master_{timeout,wait_no_slave}&lt;/h2>
&lt;p>Above, I write that there are situations where a transaction will be prevented from committing. One of those situations is when there are no semi-sync slaves or when those slaves are not acknowledging transactions (for any good or bad reasons). There are two parameters to bypass this: &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-options-master.html#sysvar_rpl_semi_sync_master_wait_no_slave" target="_blank" rel="noopener noreferrer">rpl_semi_sync_master_wait_no_slave&lt;/a> and &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-options-master.html#sysvar_rpl_semi_sync_master_timeout" target="_blank" rel="noopener noreferrer">rpl_semi_sync_master_timeout&lt;/a>. Let’s talk about these a little.&lt;/p>
&lt;p>The rpl_semi_sync_master_wait_no_slave parameter allows MySQL to bypass the semi-sync wait when there are not enough semi-sync slaves (semi-sync in MySQL 5.7 can wait for more than one slave and this behavior is controlled by the &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-options-master.html#sysvar_rpl_semi_sync_master_wait_for_slave_count" target="_blank" rel="noopener noreferrer">rpl_semi_sync_master_wait_for_slave_count&lt;/a> parameter). The default value for the “wait_no_slave” parameter is ON, which means it still waits even if there are not enough semi-sync slaves. This is a safe default as it enforces the promise of semi-sync (not acknowledging COMMIT before the transaction is replicated to slaves). Even if setting this parameter to OFF is voiding that promise, I like that it exists (details below). However, I would not run MySQL unattended with waiting disabled in a full semi-sync environment.&lt;/p>
&lt;p>The rpl_semi_sync_master_timeout parameter allows MySQL to short-circuit waiting for slaves after a timeout with acknowledging COMMIT to the client event is the transaction was not replicated. Its default is 10 seconds, which I think is wrong. After 10 seconds, there are probably thousands of transactions waiting for commit on the master and MySQL is already struggling. If we want to prevent MySQL from struggling, this parameter should be lower. However, if we want a zero-loss failover (and failover is taking more than 10 seconds), we should not commit transactions without replicating them to slaves, in which case this parameter should be higher. Higher or lower, which one should be used…&lt;/p>
&lt;p>Using a “low” value for rpl_semi_sync_master_timeout looks very strange to me in a full semi-sync environment. It looks like the DBA cannot choose between committing as often as possible (standard non-semi-sync replication) or only committing transactions that are replicated (semi-sync). There is no way to have the best of both worlds here:&lt;/p>
&lt;ul>
&lt;li>either someone wants &lt;strong>high success rate on commit&lt;/strong>, which means that the DBA does not deploy semi-sync (and the cost of this is to lose committed transactions on failover),&lt;/li>
&lt;li>or someone wants &lt;strong>high persistence on committed transactions&lt;/strong>, in which case the DBA deploys semi-sync at the cost of lowering the probability of a successful commit (and increasing commit latency).&lt;/li>
&lt;/ul>
&lt;p>I see one situation where these parameters are useful: transitioning from a non-semi-sync environment to a full semi-sync environment. During this transition, we want to learn about the new restrictions of semi-sync without causing too much disruption in production, and these parameters come in handy here. But once in a full semi-sync deployment, where we fully want to avoid loosing committed transactions when a master crash, I would not consider it a good idea to let transactions commit without being replicated to slaves.&lt;/p>
&lt;p>As a last comment on this, there are thoughts that a full semi-sync enabled master should probably crash itself when it is blocked for too long in waiting for slave acknowledgment. This is an interesting idea as it is the only way that MySQL has to unblock clients. I am not sure if this is implemented in some variant of MySQL though (maybe the Facebook variant).&lt;/p>
&lt;p>I hope this post clarified semi-sync and lossless semi-sync replication. If you still have questions about this or on related subjects, feel free to post them in the comments below.&lt;/p></content:encoded><author>Jean-François Gagné</author><category>Galera</category><category>InnoDB</category><category>MariaDB</category><category>MySQL</category><category>Percona Server</category><category>Percona XtraDB Cluster</category><category>Replication</category><media:thumbnail url="https://percona.community/blog/2018/08/semi-sync-replication-MySQL_hu_73ec11f75c462b8b.jpg"/><media:content url="https://percona.community/blog/2018/08/semi-sync-replication-MySQL_hu_3e58863ae8ce0efe.jpg" medium="image"/></item><item><title>Easy and Effective Way of Building External Dictionaries for ClickHouse with Pentaho Data Integration Tool</title><link>https://percona.community/blog/2018/08/02/easy-effective-building-external-dictionaries-clickhouse-pentaho-data-integration-tool/</link><guid>https://percona.community/blog/2018/08/02/easy-effective-building-external-dictionaries-clickhouse-pentaho-data-integration-tool/</guid><pubDate>Thu, 02 Aug 2018 16:09:26 UTC</pubDate><description>In this post, I provide an illustration of how to use Pentaho Data Integration (PDI) tool to set up external dictionaries in MySQL to support ClickHouse. Although I use MySQL in this example, you can use any PDI supported source.</description><content:encoded>&lt;p>In this post, I provide an illustration of how to use Pentaho Data Integration (PDI) tool to set up external dictionaries in MySQL to support ClickHouse. Although I use MySQL in this example, you can use any PDI supported source.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/pentaho-clickhouse.jpg" alt="pentaho pdt with clickhouse" />&lt;/figure>&lt;/p>
&lt;h2 id="clickhouse">ClickHouse&lt;/h2>
&lt;p>ClickHouse is an open-source column-oriented DBMS (columnar database management system) for online analytical processing. Source: &lt;a href="https://en.wikipedia.org/wiki/ClickHouse" target="_blank" rel="noopener noreferrer">wiki&lt;/a>.&lt;/p>
&lt;h2 id="pentaho-data-integration">Pentaho Data Integration&lt;/h2>
&lt;p>Information from the Pentaho &lt;a href="https://wiki.pentaho.com/display/EAI/Pentaho+Data+Integration+%28Kettle%29+Tutorial" target="_blank" rel="noopener noreferrer">wiki&lt;/a>: Pentaho Data Integration (PDI, also called Kettle) is the component of Pentaho responsible for the Extract, Transform and Load (ETL) processes. Though ETL tools are most frequently used in data warehouses environments, PDI can also be used for other purposes:&lt;/p>
&lt;ul>
&lt;li>Migrating data between applications or databases&lt;/li>
&lt;li>Exporting data from databases to flat files&lt;/li>
&lt;li>Loading data massively into databases&lt;/li>
&lt;li>Data cleansing&lt;/li>
&lt;li>Integrating applications&lt;/li>
&lt;/ul>
&lt;p>PDI is easy to use. Every process is created with a graphical tool where you specify what to do without writing code to indicate how to do it; because of this, you could say that PDI is &lt;em>metadata oriented&lt;/em>.&lt;/p>
&lt;h2 id="external-dictionaries">External dictionaries&lt;/h2>
&lt;p>You can add your own dictionaries from various data sources. The data source for a dictionary can be a local text or executable file, an HTTP(s) resource, or another DBMS. For more information, see “&lt;a href="https://clickhouse.yandex/docs/en/dicts/external_dicts_dict_sources/#dicts-external_dicts_dict_sources" target="_blank" rel="noopener noreferrer">Sources for external dictionaries&lt;/a>”. ClickHouse:&lt;/p>
&lt;ul>
&lt;li>Fully or partially stores dictionaries in RAM.&lt;/li>
&lt;li>Periodically updates dictionaries and dynamically loads missing values. In other words, dictionaries can be loaded dynamically.&lt;/li>
&lt;/ul>
&lt;p>The configuration of external dictionaries is located in one or more files. The path to the configuration is specified in the &lt;a href="https://clickhouse.yandex/docs/en/operations/server_settings/settings/#server_settings-dictionaries_config" target="_blank" rel="noopener noreferrer">dictionaries_config&lt;/a> parameter. Dictionaries can be loaded at server startup or at first use, depending on the &lt;a href="https://clickhouse.yandex/docs/en/operations/server_settings/settings/#server_settings-dictionaries_lazy_load" target="_blank" rel="noopener noreferrer">dictionaries_lazy_load&lt;/a> setting. Source: &lt;a href="https://clickhouse.yandex/docs/en/query_language/dicts/" target="_blank" rel="noopener noreferrer">dictionaries&lt;/a>.&lt;/p>
&lt;h3 id="example-of-external-dictionary">Example of external dictionary&lt;/h3>
&lt;p>In two words, dictionary is a key(s)-value(s) mapping that could be used for storing some value(s) which will be retrieved using a key. It is a way to build a “star” schema, where &lt;em>dictionaries are dimensions&lt;/em>:
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/example-external-dictionary.jpg" alt="example external dictionary" />&lt;/figure> Using dictionaries you can lookup data by key(customer_id in this example). Why do not use tables for simple JOIN? Here is what documentation says:&lt;/p>
&lt;blockquote>
&lt;p>If you need a JOIN for joining with dimension tables (these are relatively small tables that contain dimension properties, such as names for advertising campaigns), a JOIN might not be very convenient due to the bulky syntax and the fact that the right table is re-accessed for every query. For such cases, there is an “external dictionaries” feature that you should use instead of JOIN. For more information, see the section “External dictionaries”.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;h5 id="main-point-of-this-blog-post">Main point of this blog post:&lt;/h5>
&lt;blockquote>
&lt;p>Demonstrating filling a MySQL table using PDI tool and connecting this table to ClickHouse as an external dictionary. You can create a scheduled job for loading or updating this table.&lt;/p>&lt;/blockquote>&lt;/blockquote>
&lt;p>Filling dictionaries during the ETL process is a challenge. Of course you can write a script (or scripts) that will do all of this, but I’ve found a better way. Benefits:&lt;/p>
&lt;ul>
&lt;li>Self-documented: you see what exactly PDI job does;&lt;/li>
&lt;li>Easy to modify(see example below)&lt;/li>
&lt;li>Built-in logging&lt;/li>
&lt;li>Very flexible&lt;/li>
&lt;li>If you use the &lt;a href="https://wiki.pentaho.com/display/COM/Community+Edition+Downloads" target="_blank" rel="noopener noreferrer">Community Edition&lt;/a> you will not pay anything.&lt;/li>
&lt;/ul>
&lt;h2 id="pentaho-data-integration-part">Pentaho Data Integration part&lt;/h2>
&lt;p>You need a UI for running/developing ETL, but it’s not necessary to use the UI for running a transformation or job. Here’s an example of running it from a Linux shell(read PDI’s docs about jobs/transformation):&lt;/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">${PDI_FOLDER}/kitchen.sh -file=${PATH_TO_PDI_JOB_FILE}.kjb [-param:SOMEPARAM=SOMEVALUE]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">${PDI_FOLDER}/pan.sh -file=${PATH_TO_PDI_TRANSFORMATION_FILE}.ktr [-param:SOMEPARAM=SOMEVALUE]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Here is a PDI transformation. In this example I use three tables as a source of information, but you can create very complex logic:
&lt;figure>
&lt;img sizes="100vw" srcset="https://percona.community/blog/2018/08/pdi-transformation_hu_e8066f1ac7a9a3fe.png 480w, https://percona.community/blog/2018/08/pdi-transformation_hu_fdb1807e374723c6.png 768w, https://percona.community/blog/2018/08/pdi-transformation_hu_4ce0782f6b6e0830.png 1400w"
src="https://percona.community/blog/2018/08/pdi-transformation.png" alt="PDI transformation" />&lt;/figure>&lt;/p>
&lt;h3 id="datasource1-definition-example">“Datasource1” definition example&lt;/h3>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/datasource-definition-example.png" alt="datasource definition example" />&lt;/figure>&lt;/p>
&lt;p>Dimension lookup/update is a step that updates the MySQL table (in this example, it could be any database supported by PDI output step). It will be the source for ClickHouse’s external dictionary:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/dimension-lookup-update-id-1.png" alt="dimension lookup update id " />&lt;/figure>&lt;/p>
&lt;p>Fields definition:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/dimension-lookup-update-fields-2.png" alt="dimension fields definition" />&lt;/figure>&lt;/p>
&lt;p>Once you have done this, you hit the “SQL” button and it will generate the DDL code for D_CUSTOMER table. You can manage the algorithm of storing data in the step above: update or insert new record(with time_start/time_end fields). Also, if you use PDI for ETL, then you can generate a “technical key” for your dimension and store this key in ClickHouse, this is a different story… For this example, I will use “id” as a key in the ClickHouse dictionary.&lt;/p>
&lt;p>The last step is setting up external dictionary in ClickHouse’s server config.&lt;/p>
&lt;h3 id="the-clickhouse-part">The ClickHouse part&lt;/h3>
&lt;p>External dictionary config, in this example you’ll see that I use 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-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;dictionaries>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;dictionary>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;name>customers&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;source>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;!-- Source configuration -->
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;mysql>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;port>3306&lt;/port>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;user>MySQL_User&lt;/user>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;password>MySQL_Pass&lt;/password>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;replica>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;host>MySQL_host&lt;/host>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;priority>1&lt;/priority>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/replica>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;db>DB_NAME&lt;/db>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;table>D_CUSTOMER&lt;/table>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/mysql>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/source>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;layout>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;!-- Memory layout configuration -->
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;flat/>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/layout>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;structure>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;id>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;name>id&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/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;attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;name>name&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;type>String&lt;/type>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;null_value>&lt;/null_value>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;name>address&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;type>String&lt;/type>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;null_value>&lt;/null_value>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;!-- Will be uncommented later
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;name>phone&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;type>String&lt;/type>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;null_value>&lt;/null_value>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/attribute>
&lt;/span>&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;/structure>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;lifetime>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;min>3600&lt;/min>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;max>86400&lt;/max>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;/lifetime>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/dictionary>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;/dictionaries>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Creating the fact table in ClickHouse:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/table-in-ClickHouse.png" alt="Create table in ClickHouse" />&lt;/figure>&lt;/p>
&lt;p>Some sample data:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/sample-data.png" alt="Sample data" />&lt;/figure>&lt;/p>
&lt;p>Now we can fetch data aggregated against the customer name:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/aggregated-data-with-customer-name.png" alt="aggregated data with customer name" />&lt;/figure>&lt;/p>
&lt;h3 id="dictionary-modification">Dictionary modification&lt;/h3>
&lt;p>Sometimes, it happens that you need to modify your dimensions. In my example I am going to add phone number to the “customers” dictionary. Not a problem at all. You update your datasource in PDI job:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/dictionary-modification.png" alt="dictionary modification add new field " />&lt;/figure>&lt;/p>
&lt;p>Open the “Dimension lookup/update” step and add the &lt;em>phone&lt;/em> field:&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/add-a-field.png" alt="Add a field " />&lt;/figure>&lt;/p>
&lt;p>And hit the SQL button.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/alter-data-statement.png" alt="alter table statement" />&lt;/figure>&lt;/p>
&lt;p>Also add the “phone” field in ClickHouse’s dictionary 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-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;attribute>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">       &lt;name>phone&lt;/name>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">               &lt;type>String&lt;/type>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">               &lt;null_value>&lt;/null_value>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">   &lt;/attribute>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>ClickHouse will update a dictionary on the fly and we are ready to go—if not please check the logs. Now you can run the query without a modification of fact_table:
&lt;figure>&lt;img src="https://percona.community/blog/2018/08/query-without-modifying-fact.png" alt="query without modifying fact" />&lt;/figure>&lt;/p>
&lt;p>Also, note that PDI job is an XML file that could be put under version source control tools, so it is easy to track or rollback if needed. Please do not hesitate to ask if you have questions!&lt;/p></content:encoded><author>Timur Solodovnikov</author><category>ClickHouse</category><category>Data Warehouse</category><category>MySQL</category><category>Open Source Databases</category><category>Tools</category><media:thumbnail url="https://percona.community/blog/2018/08/pentaho-clickhouse_hu_7cfbcd393858d937.jpg"/><media:content url="https://percona.community/blog/2018/08/pentaho-clickhouse_hu_8b2d3d847705586b.jpg" medium="image"/></item><item><title>How to Automate Minor Version Upgrades for MySQL on RDS</title><link>https://percona.community/blog/2018/07/10/automate-minor-version-upgrades-mysql-rds/</link><guid>https://percona.community/blog/2018/07/10/automate-minor-version-upgrades-mysql-rds/</guid><pubDate>Tue, 10 Jul 2018 12:19:11 UTC</pubDate><description>Amazon RDS for MySQL offers the option to automate minor version upgrades using the minor version upgrade policy, a property that lets you decide if Amazon is allowed to perform the upgrades on your behalf. Usually the goal is not to upgrade automatically every RDS instance but to keep up to date automatically non-production deployments. This helps you address engine issues as soon as possible and improve the automation of the deployment process.</description><content:encoded>&lt;p>Amazon RDS for MySQL offers the option to automate &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html#USER_UpgradeDBInstance.MySQL.Minor" target="_blank" rel="noopener noreferrer">minor version upgrades&lt;/a> using the &lt;em>minor version upgrade policy&lt;/em>, a property that lets you decide if Amazon is allowed to perform the upgrades on your behalf. Usually the goal is not to upgrade automatically every RDS instance but to keep up to date automatically non-production deployments. This helps you address engine issues as soon as possible and improve the automation of the deployment process.&lt;/p>
&lt;p>If your are using the AWS Command Line Interface (CLI) and you have an instance called &lt;em>test-rds01&lt;/em> it is as simple as changing&lt;/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">[--auto-minor-version-upgrade | --no-auto-minor-version-upgrade]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>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-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">aws rds modify-db-instance --db-instance-identifier test-rds01 --apply-immediately
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--auto-minor-version-upgrade true&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And if you use the AWS Management Console, it is just a check box. All sorted? Unfortunately not. The main problem is that Amazon performs those upgrade only in rare circumstances.&lt;/p>
&lt;p>As for Amazon’s &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html#USER_UpgradeDBInstance.MySQL.Minor" target="_blank" rel="noopener noreferrer">documentation&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Minor version upgrades only occur automatically if a minor upgrade replaces an unsafe version, such as a minor upgrade that contains bug fixes for a previous version. In all other cases, you must modify the DB instance manually to perform a minor version upgrade.&lt;/p>&lt;/blockquote>
&lt;p>If the new version fixes any vulnerabilities that were present in the previous version, then the auto minor version upgrade will automatically take place during the next weekly maintenance window on your DB instance. In all other cases, you should manually perform the minor version upgrade. So in most scenarios, the automatic upgrade is unlikely to happen and using the auto-minor-version-upgrade  attribute is not the way to keep your MySQL running on RDS updated to the latest available minor version.&lt;/p>
&lt;h4 id="how-to-improve-automation-of-minor-version-upgrades-amazon-rds-for-mysql">How to improve automation of minor version upgrades Amazon RDS for MySQL&lt;/h4>
&lt;p>Let’s say you want to reduce the time a newer minor version reaches your development environments or even your production ones. How can you achieve that on RDS? First of all you have to consider the delay it takes for a minor version to reach RDS that can be anything between a few weeks and a few months.  And you might even not notice that a new minor is available as it is not obvious how to be notified when it is.&lt;/p>
&lt;p>&lt;strong>What is the best way to be notified of new minor versions available on RDS MySQL?&lt;/strong>&lt;/p>
&lt;p>In the past you could (even automatically) monitor the &lt;a href="https://aws.amazon.com/releasenotes/?tag=releasenotes%23keywords%23amazon-rds" target="_blank" rel="noopener noreferrer">release notes page&lt;/a> but the page is not anymore used for RDS. Now you have to monitor the &lt;a href="https://aws.amazon.com/new/#database-services" target="_blank" rel="noopener noreferrer">database announcement page&lt;/a>, something that you can hardly automate.&lt;/p>
&lt;p>&lt;strong>Any way to speed up the minor version upgrades?&lt;/strong>&lt;/p>
&lt;p>You can use the AWS CLI invoking the &lt;em>describe-db-engine-versions&lt;/em> API or write a simple Lambda function to retrieve the latest available minor version and act accordingly: you can, for example, notify your team of DBAs using Amazon Simple Notification Service (SNS) or you can automatically upgrade the instance. Let’s first see how to achieve that using the command 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-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">aws --profile sandbox rds describe-db-engine-versions --engine 'mysql' --engine-version '5.7'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--query "DBEngineVersions[-1].EngineVersion"&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>where the -1 in the array let you filter out the very latest version of the engine available on RDS. Today the result is “5.7.21” and a simple cron job will monitor and can trigger notification for changes. Note that the same approach can be used to retrieve the latest available minor version for engines running MySQL 5.5 and MySQL 5.6. And PostgreSQL engines too.&lt;/p>
&lt;p>If you want to automatically and immediately upgrade your instance, the logic can be easily done in a few lines in bash with a cron on a EC2. For example, the following function requires only the database instance identifier:&lt;/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">rds_minor_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"> rds_endpoint=$1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> engine_version="5.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"> rds_current_minor=$(aws rds describe-db-instances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --db-instance-identifier="$rds_endpoint" --query "DBInstances[].EngineVersion")
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rds_latest_minor=$(aws rds describe-db-engine-versions -- engine 'mysql'
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --engine-version $eng_version --query "DBEngineVersions[-1].EngineVersion")
&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 [ "$rds_latest_minor" != "$rds_current_minor" ]; then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> aws rds modify-db-instance --apply-immediately --engine-version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $rds_latest_minor --db-instance-identifier $rds_endpoint
&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">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Alternatively you can write the code as a scheduled Lambda function in your favourite language. For example, using the AWS node.js SDK you can &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/RDS.html" target="_blank" rel="noopener noreferrer">manage RDS&lt;/a> and implement the logic above using the &lt;em>rds.describeDBEngineVersions&lt;/em> and_rds.modifyDBInstance_ to achieve the same.&lt;/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">rds.describeDBEngineVersions(params, function(err, 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">});
&lt;/span>&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">var params = {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DBInstanceIdentifier: 'test-rds01',
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ApplyImmediately: true,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EngineVersion: '&lt;new minor 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>&lt;/span>&lt;span class="line">&lt;span class="cl">rds.modifyDBInstance(params, function(err, 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">});&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/div>
&lt;/div>
&lt;h4 id="speed-up-your-minor-upgrade">Speed up your minor upgrade!&lt;/h4>
&lt;p>To summarize, Amazon Web Services does not offer a real way to automatically upgrade a RDS instance to the latest available minor in the most common scenarios, but it is very easy to achieve that by taking advantage of the AWS CLI or the many SDKs.&lt;/p>
&lt;p>The goal is not to upgrade automatically every deployment. You would not normally use this for production deployments. However, being able to monitor the latest available minor version on RDS and apply the changes automatically for development and staging deployment can significantly reduce the time it takes to have MySQL up to date on RDS and make your upgrade process more automated.&lt;/p>
&lt;p>
&lt;figure>&lt;img src="https://percona.community/blog/2018/07/upgrade-minor-versions-MySQL-Amazon-RDS.jpg" alt="upgrade minor versions MySQL Amazon RDS" />&lt;/figure>&lt;/p></content:encoded><author>Renato Losio</author><category>Amazon RDS</category><category>AWS</category><category>DevOps</category><category>MySQL</category><category>RDS</category><category>Upgrade</category><media:thumbnail url="https://percona.community/blog/2018/07/upgrade-minor-versions-MySQL-Amazon-RDS_hu_a12a8e21cdc46c96.jpg"/><media:content url="https://percona.community/blog/2018/07/upgrade-minor-versions-MySQL-Amazon-RDS_hu_4b895255f425a679.jpg" medium="image"/></item><item><title>A Nice Feature in MariaDB 10.3: no InnoDB Buffer Pool in Core Dumps</title><link>https://percona.community/blog/2018/06/28/nice-feature-in-mariadb-103-no-innodb-buffer-pool-in-coredumps/</link><guid>https://percona.community/blog/2018/06/28/nice-feature-in-mariadb-103-no-innodb-buffer-pool-in-coredumps/</guid><pubDate>Thu, 28 Jun 2018 12:28:58 UTC</pubDate><description>MariaDB 10.3 is now generally available (10.3.7 was released GA on 2018-05-25). The article What’s New in MariaDB Server 10.3 by the MariaDB Corporation lists three key improvements in 10.3: temporal data processing, Oracle compatibility features, and purpose-built storage engines. Even if I am excited about MyRocks and curious on Spider, I am also very interested in less flashy but still very important changes that make running the database in production easier. This post describes such improvement: no InnoDB Buffer Pool in core dumps.</description><content:encoded>&lt;p>MariaDB 10.3 is now generally available (10.3.7 was released GA on 2018-05-25). The article &lt;a href="https://mariadb.com/resources/blog/whats-new-mariadb-server-103" target="_blank" rel="noopener noreferrer">What’s New in MariaDB Server 10.3&lt;/a> by the MariaDB Corporation lists three key improvements in 10.3: temporal data processing, Oracle compatibility features, and purpose-built storage engines. Even if I am excited about &lt;a href="https://mariadb.com/kb/en/library/myrocks/" target="_blank" rel="noopener noreferrer">MyRocks&lt;/a> and curious on &lt;a href="https://mariadb.com/kb/en/library/spider-storage-engine-overview/" target="_blank" rel="noopener noreferrer">Spider&lt;/a>, I am also very interested in less flashy but still very important changes that make running the database in production easier. This post describes such improvement: &lt;strong>no&lt;/strong> &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html" target="_blank" rel="noopener noreferrer">&lt;strong>InnoDB Buffer Pool&lt;/strong>&lt;/a> &lt;strong>in core dumps&lt;/strong>.&lt;/p>
&lt;p>Hidden in the &lt;em>Compression&lt;/em> section of the page &lt;a href="https://mariadb.com/kb/en/library/changes-improvements-in-mariadb-103/" target="_blank" rel="noopener noreferrer">Changes &amp; Improvements in MariaDB 10.3&lt;/a> from the &lt;a href="https://mariadb.com/kb/" target="_blank" rel="noopener noreferrer">Knowledge Base&lt;/a>, we can read:&lt;/p>
&lt;blockquote>
&lt;p>On Linux, shrink the core dumps by omitting the InnoDB buffer pool&lt;/p>&lt;/blockquote>
&lt;p>This is it, no more details, only a link to &lt;a href="https://jira.mariadb.org/browse/MDEV-10814" target="_blank" rel="noopener noreferrer">MDEV-10814 (Feature request: Optionally exclude large buffers from core dumps)&lt;/a>. This Jira ticket was open in 2016-09-15 by a well-known MariaDB Support Engineer: Hartmut Holzgraefe. I know Booking.com was asking for this feature for a long time, this is even mentioned by Hartmut in a &lt;a href="https://github.com/MariaDB/server/pull/333#issuecomment-296206130" target="_blank" rel="noopener noreferrer">GitHub comment&lt;/a>.&lt;/p>
&lt;p>The ways this feature eases operations with MariaDB are well documented by Hartmut in the description of the Jira ticket:&lt;/p>
&lt;ul>
&lt;li>it needs less available disk space to store core dumps,&lt;/li>
&lt;li>it reduces the time required to write core dumps (and hence restart MySQL after a crash),&lt;/li>
&lt;li>it improves security by omitting substantial amount of user data from core dumps.&lt;/li>
&lt;/ul>
&lt;p>In addition to that, I would add that smaller core dumps are easier to share in tickets. I am often asked by support engineers to provide a core dump in relation to a crash, and my reply is “&lt;em>How do you want me to give you with a 192 GB file ?&lt;/em>” (or even bigger files as I saw MySQL/MariaDB being used on servers with 384 GB of RAM). This often leads to a “&lt;em>Let me think about this and I will come back to you&lt;/em>” answer. Avoiding the InnoDB Buffer Pool in core dumps makes this less of an issue for both DBAs and support providers.&lt;/p>
&lt;p>Before continuing the discussion on this improvement, I need to give more details about what a core dump is.&lt;/p>
&lt;h4 id="what-is-a-core-dump-and-why-is-it-useful">&lt;strong>What is a Core Dump and Why is it Useful ?&lt;/strong>&lt;/h4>
&lt;p>By looking at the &lt;a href="http://man7.org/linux/man-pages/man5/core.5.html" target="_blank" rel="noopener noreferrer">Linux manual page for core (and core dump file)&lt;/a>, we can read:&lt;/p>
&lt;blockquote>
&lt;p>[A core dump is] a disk file containing an image of the process’s memory at the time of termination. This image can be used in a debugger to inspect the state of the program at the time that it terminated.&lt;/p>&lt;/blockquote>
&lt;p>The &lt;a href="https://en.wikipedia.org/wiki/Core_dump" target="_blank" rel="noopener noreferrer">Wikipedia article for core dump&lt;/a> also tells us that:&lt;/p>
&lt;ul>
&lt;li>the core dump includes key pieces of program state as processor registers, memory management details, and other processor and operating system flags and information,&lt;/li>
&lt;li>the name comes from &lt;a href="https://en.wikipedia.org/wiki/Magnetic_core_memory" target="_blank" rel="noopener noreferrer">magnetic core memory&lt;/a>, the principal form of random access memory from the 1950s to the 1970s, and the name has remained even if magnetic core technology is obsolete.&lt;/li>
&lt;/ul>
&lt;p>So a core dump is a file that can be very useful to understand the context of a crash. The exact details of how to use a core dump have been already discussed in many places and is beyond the subject of this post. The interested reader can learn more by following those links:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.percona.com/blog/2011/08/26/getting-mysql-core-file-on-linux/" target="_blank" rel="noopener noreferrer">Getting MySQL Core file on Linux&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://mariadb.com/kb/en/library/how-to-produce-a-full-stack-trace-for-mysqld/" target="_blank" rel="noopener noreferrer">How to Produce a Full Stack Trace for mysqld&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.percona.com/blog/2015/08/17/mysql-is-crashing-a-support-engineers-point-of-view/" target="_blank" rel="noopener noreferrer">MySQL is crashing: a support engineer’s point of view&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.dropbox.com/s/j4salsgphyrsnjw/Cheat%20Sheet.pdf" target="_blank" rel="noopener noreferrer">Database issue cheat sheet (including gdb commands for using core dumps)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/crashing.html" target="_blank" rel="noopener noreferrer">What to Do If MySQL Keeps Crashing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/using-gdb-on-mysqld.html" target="_blank" rel="noopener noreferrer">Debugging mysqld under gdb&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Update 2018-07-31&lt;/strong>: more links about how to use core dumps:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://mysqlentomologist.blogspot.com/2017/08/how-to-find-values-of-session-variables.html" target="_blank" rel="noopener noreferrer">How to Find Values of Session Variables With gdb&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mysqlentomologist.blogspot.com/2017/07/how-to-find-processlist-thread-id-in-gdb.html" target="_blank" rel="noopener noreferrer">How to Find Processlist Thread id in gdb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://archive.fosdem.org/2015/schedule/event/mysql_gdb/attachments/slides/595/export/events/attachments/mysql_gdb/slides/595/FOSDEM2015_gdb_tips_and_tricks_for_MySQL_DBAs.pdf" target="_blank" rel="noopener noreferrer">gdb tips and tricks for MySQL DBAs&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Now that we know more about core dumps, we can get back to the discussion of the new feature.&lt;/p>
&lt;h4 id="the-no-innodb-buffer-pool-in-core-dump-feature-from-mariadb-103">&lt;strong>The&lt;/strong> &lt;strong>&lt;em>no InnoDB Buffer Pool in Core Dump&lt;/em>&lt;/strong> &lt;strong>Feature from MariaDB 10.3&lt;/strong>&lt;/h4>
&lt;p>As already pointed out above, there are very few details in the release notes about how this feature works. By digging in &lt;a href="https://jira.mariadb.org/browse/MDEV-10814" target="_blank" rel="noopener noreferrer">MDEV-10814&lt;/a>, following pointers to pull requests (#&lt;a href="https://github.com/MariaDB/server/pull/333" target="_blank" rel="noopener noreferrer">333&lt;/a>, #&lt;a href="https://github.com/MariaDB/server/pull/364" target="_blank" rel="noopener noreferrer">364&lt;/a>, &lt;a href="https://github.com/MariaDB/server/pull/365" target="_blank" rel="noopener noreferrer">365&lt;/a>, …), and reading the &lt;a href="https://github.com/MariaDB/server/pull/364/commits/b600f30786816e33c1706dd36cdabf21034dc781" target="_blank" rel="noopener noreferrer">commit message&lt;/a>, I was able to gather this:&lt;/p>
&lt;ul>
&lt;li>An initial patch was written by Hartmut in 2015.&lt;/li>
&lt;li>It uses the MADV_DONTDUMP flag to the &lt;a href="http://man7.org/linux/man-pages/man2/madvise.2.html" target="_blank" rel="noopener noreferrer">madvise&lt;/a> system call (available in Linux kernel 3.4 and higher).&lt;/li>
&lt;li>Hartmut’s patch was rebased by Daniel Black, a well-known MariaDB Community Contributor (pull request #&lt;a href="https://github.com/MariaDB/server/pull/333" target="_blank" rel="noopener noreferrer">333&lt;/a>).&lt;/li>
&lt;li>The first work by Daniel had a configuration parameter to allow including/excluding the InnoDB Buffer Pool in/from core dumps, but after a &lt;a href="https://github.com/MariaDB/server/pull/333#issuecomment-295460913" target="_blank" rel="noopener noreferrer">discussion&lt;/a> in pull request #333, it was decided that the RELEASE builds would not put the InnoDB Buffer Pool in core dumps and that &lt;a href="https://mariadb.com/kb/en/library/compiling-mariadb-for-debugging/" target="_blank" rel="noopener noreferrer">DEBUG builds&lt;/a> would include it (more about this below).&lt;/li>
&lt;li>The function buf_madvise_do_dump is added but never invoked by the server; it is there to be called from a debugger to re-enable full core dumping if needed (from this &lt;a href="https://github.com/MariaDB/server/pull/364/commits/b600f30786816e33c1706dd36cdabf21034dc781" target="_blank" rel="noopener noreferrer">commit message&lt;/a>).&lt;/li>
&lt;li>The &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-redo-log-buffer.html" target="_blank" rel="noopener noreferrer">InnoDB Redo Log buffer&lt;/a> is also excluded from core dumps (from this &lt;a href="https://github.com/MariaDB/server/pull/364#issuecomment-345655419" target="_blank" r