Dev & EngARTICLE

Percona Operator for MySQL 1.3.0 brings automatic tuning and zero-data-loss failover

Article · Dev & Eng
Reading
7 min
Code
2
In short

Percona Operator for MySQL 1.3.0 now tunes MySQL configuration based on pod size, recovers lost transactions during asynchronous cluster failures, and encrypts the binlogs used for point-in-time recovery.

Percona released Percona Operator for MySQL 1.3.0 on October 29, 2026, according to the company's official blog post. The operator is the package that manages MySQL clusters inside Kubernetes, and this version touches three points that any team running a database in containers feels on a daily basis: automatic MySQL configuration, zero-transaction-loss failover in asynchronous clusters, and point-in-time recovery (PITR) over encrypted binlogs.

Before any new feature, two default behaviors changed, and whoever upgrades needs to review both.

What breaks silently during the upgrade

The operator's leader election now comes enabled by default. For the vast majority of deployments this is the correct behavior; anyone who kept a single controller replica for a specific reason (local environment, debugging) needs to explicitly set PSO_LEADER_ELECTION_ENABLED=false in the operator's Deployment.

The change that catches people off guard is different: asynchronous clusters now require backup to be enabled. The reason is technical, not bureaucratic: the zero-data-loss failover, detailed below, depends on reading the binary logs of the primary that went down, and the operator uses the same backup infrastructure for that. Without backup configured, the CRD refuses to reconcile the asynchronous cluster.

There are two ways out:

  • Enable backup on the cluster, the path recommended by Percona itself.
  • Explicitly disable the protection with the escape flag.
yaml
spec:
  unsafeFlags:
    asyncFailoverWithPossibleDataLoss: true

This second option restores the behavior prior to 1.3.0: fast failover, but transactions committed on the old primary that had not yet reached the chosen replica are lost. Group Replication clusters are not affected by either change.

Autopilot tuning: the operator calculates my.cnf based on pod size

Tuning MySQL parameters to a pod's shape is work that every team does once and then forgets to repeat. When the pod's CPU or memory limit changes, manual configuration almost never keeps up. The spec.mysql.autoConfig block solves this: the operator now calculates MySQL configuration based on the resources declared in the pod and the workload type provided.

Three fields control the behavior: enabled turns the feature on, loadType describes the load profile (mostlyReads, someWrites, equalReadsWrites, or heavyWrites), and version indicates which MySQL version is being tuned. The CRD requires mysql.resources to include CPU and memory limits when autoConfig.enabled is true: without these limits the operator has no basis for calculation.

yaml
apiVersion: ps.percona.com/v1
kind: PerconaServerMySQL
metadata:
  name: cluster1
spec:
  mysql:
    size: 3
    resources:
      limits:
        memory: 8Gi
        cpu: 4
    autoConfig:
      enabled: true
      loadType: someWrites
      version: 8.4.0

With this block, the operator sizes the buffer pool and related parameters based on 8 GiB of memory and 4 CPUs, tilts the configuration toward a mixed load with some writes, and adjusts everything for MySQL 8.4.0. Changing the resource limit or the loadType makes the operator recalculate automatically.

Not every variable accepts a hot change. Where MySQL allows it, the operator applies the change with SET GLOBAL, without restarting the pod; static variables still require a restart, which the operator performs pod by pod, in sequence, so as not to take the cluster down all at once.

Failing without losing data: what happens between the crash and the promotion

Asynchronous replication, by definition, confirms the transaction to the client before any replica has received it. If the primary goes down with transactions in the binlog that no replica has read yet, those transactions disappear, even with a well-configured high-availability controller. This is the gap that 1.3.0 addresses.

The operator extends the xtrabackup sidecar, present in every MySQL pod, with a POST /failover/stream endpoint on port 6450, which serves the binary logs as a tar stream. When the primary fails, the failover tool runs on the chosen replica and follows a rescue sequence:

  1. Probes the failed primary and backs off if it came back on its own.
  2. Stops replication and flushes the relay logs.
  3. Reads Read_Source_Log_File and Read_Source_Log_Pos to find the exact gap.
  4. Fetches the missing binlogs from the fallen primary's sidecar.
  5. Trims the recovered binlogs at the last complete transaction point.
  6. Splices that segment onto the last closed relay log.
  7. Starts the SQL thread and waits until every missing transaction is applied.
  8. Only then does the orchestrator promote the chosen replica.

This has a cost: time. The failover has a default budget of one hour for the fallen primary's pod to become reachable and for the missing transactions to be applied, counted from the first attempt, not per retry. Once the deadline is exhausted, the on-timeout policy decides the outcome: Abort keeps the cluster locked waiting for intervention, ForceWithPossibleDataLoss promotes anyway. The mechanism only requires the primary's sidecar to respond, not for mysqld to be up: a pod with a stuck MySQL process but a live sidecar still serves binlog.

PITR with encrypted binlogs: the key leaves the pod

The operator's binlog server already sent binary logs to object storage to enable point-in-time recovery. Starting with 1.3.0, these files can be encrypted at rest, with the key kept in a Kubernetes Secret instead of traveling in each MySQL pod.

The scheme is envelope encryption: a per-file data encryption key (DEK) encrypts each binlog, and that DEK is wrapped under a key encryption key (KEK) that the operator reads from a keyring Secret. Three fields control this: spec.backup.pitr.binlogServer.storage.encryption.cipher sets the symmetric cipher (AES-256-CTR by default), kekId names the KEK entry within the keyring, and keyringSecret points to the Secret that holds the keyring file (default key keyring.json).

On restore, the PITR tool reads the keyring, locates the KEK referenced in the file's metadata, unwraps the key, and pipes the decrypted events through mysqlbinlog into the restored MySQL. If the keyring doesn't exist or the referenced KEK isn't in it, the restore fails fast and explicitly, instead of producing a silent partial recovery, the kind of failure that only shows up during an audit.

The rest of the package: less stuff outside the CR

Several smaller changes push manual runbook work into the declarative spec:

  • Application users are now declared in the spec.users list, with name, optional passwordSecretRef, dbs, hosts, grants, and withGrantOption.
  • The backup path accepts a custom CA, which unlocks pointing to MinIO or another S3-compatible store behind a company's own corporate CA.
  • The TLS block can reference a cert-manager ClusterIssuer instead of a namespace issuer.
  • TLS rotation without restarting the pod: the operator merges the old CA with the new one, hot-reloads the certificates, and only discards the old root after confirming propagation.
  • Backup with a deadline: a backup that exceeds the deadline fails fast with a clear status, instead of hanging.
  • Leader election parameters configurable via PSO_LEADER_ELECTION_* variables.
  • Operator log persistent across controller restarts.
  • DigitalOcean Kubernetes joins as an officially supported platform, and MySQL 9.7 enters the supported version matrix.

Replication type switching in tech preview, ClusterSet rejoin, replication lag reporting, asynchronous clusters with an even number of replicas, and asynchronous bootstrap from backup round out the list, according to the release notes.

When it's worth turning everything on, and when it isn't

autoConfig only pays off when the pod has stable, declared resource limits; in a development cluster where CPU and memory change with every deploy, constant recalculation can trigger unnecessary restarts for static variables. It's worth more in production, where the pod's shape is part of the capacity contract.

The zero-data-loss failover solves a real asynchronous replication problem, but it trades speed for integrity: the default one-hour budget is high for those who prioritize downtime over the last transaction. Teams that already accept that risk for another reason, and document it, have in the asyncFailoverWithPossibleDataLoss: true flag a path back to the previous behavior, without losing the release's other new features.

Binlog encryption for PITR has an operational cost, not a performance one: someone needs to manage the keyring and make sure it survives the pod, the namespace, and the disaster that prompted the PITR in the first place. For those who already have a key-management process feeding the Secret, it's a configuration adjustment. For those who don't, the priority is building that process before turning on encryption.cipher, because a lost keyring makes restoration completely impossible.

Translated from the Brazilian Portuguese original · Read the original