Skip to content

K8SPS-715: Replication based point-in-time recovery - #1334

Draft
egegunes wants to merge 9 commits into
mainfrom
K8SPS-642-replication
Draft

K8SPS-715: Replication based point-in-time recovery#1334
egegunes wants to merge 9 commits into
mainfrom
K8SPS-642-replication

Conversation

@egegunes

@egegunes egegunes commented May 4, 2026

Copy link
Copy Markdown
Contributor

CHANGE DESCRIPTION

Problem:
Short explanation of the problem.

Cause:
Short explanation of the root cause of the issue if applicable.

Solution:
Short explanation of the solution we are providing with this PR.

CHECKLIST

Jira

  • Is the Jira ticket created and referenced properly?
  • Does the Jira ticket have the proper statuses for documentation (Needs Doc) and QA (Needs QA)?
  • Does the Jira ticket link to the proper milestone (Fix Version field)?

Tests

  • Is an E2E test/test case added for the new feature/change?
  • Are unit tests added where appropriate?

Config/Logging/Testability

  • Are all needed new/changed options added to default YAML files?
  • Are all needed new/changed options added to the Helm Chart?
  • Did we add proper logging messages for operator actions?
  • Did we ensure compatibility with the previous version or cluster upgrade process?
  • Does the change support oldest and newest supported PS version?
  • Does the change support oldest and newest supported Kubernetes version?

Copilot AI review requested due to automatic review settings May 4, 2026 06:51
@pull-request-size pull-request-size Bot added the size/XXL 1000+ lines label May 4, 2026
Comment thread build/run-pitr-restore.sh
Comment on lines +15 to +18
--admin-address=127.0.0.1
--user=mysql
--gtid-mode=ON
--enforce-gtid-consistency=ON

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
--admin-address=127.0.0.1
--user=mysql
--gtid-mode=ON
--enforce-gtid-consistency=ON
--admin-address=127.0.0.1
--user=mysql
--gtid-mode=ON
--enforce-gtid-consistency=ON

Comment thread build/run-pitr-restore.sh
Comment on lines +21 to +26
if [[ "${PITR_METHOD}" == "replication" ]]; then
MYSQLD_ARGS+=(
--skip-replica-start
--read-only=ON
--super-read-only=ON
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
if [[ "${PITR_METHOD}" == "replication" ]]; then
MYSQLD_ARGS+=(
--skip-replica-start
--read-only=ON
--super-read-only=ON
)
if [[ ${PITR_METHOD} == "replication" ]]; then
MYSQLD_ARGS+=(
--skip-replica-start
--read-only=ON
--super-read-only=ON
)

Comment thread build/run-pitr-restore.sh
log "waiting for mysqld to be ready"
until mysqladmin -u operator -p"$(</etc/mysql/mysql-users-secret/operator)" ping --silent 2>/dev/null; do
sleep 1;
sleep 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
sleep 1
sleep 1

Comment thread build/run-pitr-restore.sh
Comment on lines +50 to +61
binlog-replay)
/opt/percona/pitr replay
;;
replication)
/opt/percona/pitr init
/opt/percona/pitr setup
/opt/percona/pitr apply
;;
*)
log "unknown PITR_METHOD: ${PITR_METHOD}"
exit 1
;;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
binlog-replay)
/opt/percona/pitr replay
;;
replication)
/opt/percona/pitr init
/opt/percona/pitr setup
/opt/percona/pitr apply
;;
*)
log "unknown PITR_METHOD: ${PITR_METHOD}"
exit 1
;;
binlog-replay)
/opt/percona/pitr replay
;;
replication)
/opt/percona/pitr init
/opt/percona/pitr setup
/opt/percona/pitr apply
;;
*)
log "unknown PITR_METHOD: ${PITR_METHOD}"
exit 1
;;

Comment thread build/run-pitr-restore.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

local ts=$(date +%Y-%m-%dT%H:%M:%S.%N%z --utc | sed 's/+0000/Z/g')
echo "${ts} 0 [Info] [K8SPS-642] [Job] $*" >&2

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new PITR selection mechanism so restores can run either the existing binlog replay flow or a new replication-based flow, wiring the choice through the Restore CRD, PITR Job env, and the PITR helper binary.

Changes:

  • Add spec.pitr.method (binlog-replay | replication) to PerconaServerMySQLRestore CRD and API types (defaulting to binlog-replay).
  • Extend PITR restore Job/env generation to pass PITR_METHOD and update the restore runner script to execute the correct PITR flow.
  • Introduce replication-method stages (init/setup/apply) in cmd/pitr and add DB helpers for channel-based replication operations.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
api/v1/perconaservermysqlrestore_types.go Adds PITR method enum and defaulting to the Restore API.
config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml CRD schema updated to include pitr.method with enum/default.
deploy/bundle.yaml Generated bundle CRD updated for pitr.method.
deploy/crd.yaml Generated CRD updated for pitr.method.
deploy/cw-bundle.yaml Generated CW bundle updated for pitr.method.
deploy/backup/restore.yaml Example restore manifest updated (commented) to mention method.
pkg/pitr/pitr.go Adds PITR_METHOD env var to the PITR restore Job.
pkg/pitr/pitr_test.go Extends PITR Job tests to assert PITR_METHOD behavior.
build/run-pitr-restore.sh Selects PITR flow based on PITR_METHOD and runs appropriate subcommands.
cmd/pitr/main.go Adds replay/init/setup/apply subcommands and replication PITR implementation.
cmd/pitr/main_test.go Adds tests for new replication stages and refactors existing test for replay.
cmd/internal/db/db.go Adds channel-aware replication helpers used by replication PITR.
cmd/bootstrap/async/async_replication.go Updates callers to new StopReplication/ResetReplication signatures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/pitr/main_test.go
Comment on lines +270 to +280
indexPath := filepath.Join(mysqlDir, hostname+"-relay-bin.index")
indexData, err := os.ReadFile(indexPath)
require.NoError(t, err, "relay log index file must exist")

indexContent := string(indexData)
assert.Contains(t, indexContent, hostname+"-relay-bin.000001")
assert.Contains(t, indexContent, hostname+"-relay-bin.000002")

wantMagic := []byte{0xfe, 0x62, 0x69, 0x6e}
for i := 1; i <= len(tc.entries); i++ {
relayLog := filepath.Join(mysqlDir, fmt.Sprintf("%s-relay-bin.%06d", hostname, i))
Comment thread cmd/pitr/main_test.go
Comment on lines +373 to +381
if tc.checkRelayLogs {
hostname, err := os.Hostname()
require.NoError(t, err)
for i, wantContent := range []string{"binlogdata1", "binlogdata2"} {
relayLog := filepath.Join(mysqlDir, fmt.Sprintf("%s-relay-bin.%06d", hostname, i+1))
data, err := os.ReadFile(relayLog)
require.NoErrorf(t, err, "relay log %d must exist", i+1)
assert.Equalf(t, wantContent, string(data), "relay log %d content mismatch", i+1)
}
Comment thread cmd/pitr/main.go
Comment on lines +436 to +443
if pitrType == "date" {
lastRelayLog := relayLogName(hostname, pitrChannelName, len(entries))
lastRelayLogPath := filepath.Join(mysqlDir, lastRelayLog)
pitrGTID, err = getGTIDByDatetime(lastRelayLogPath, pitrDate)
if err != nil {
return fmt.Errorf("get latest GTID for date %s: %w", pitrDate, err)
}
log.Printf("latest GTID for date %s: %s", pitrDate, pitrGTID)
Comment thread cmd/pitr/main.go
Comment on lines +591 to +600
cmd := exec.Command("bash", "-c",
fmt.Sprintf("mysqlbinlog --stop-datetime='%s' %s | grep GTID_NEXT | grep -v AUTOMATIC | tail -n 1",
stopDatetime, relayLogPath))

output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to execute mysqlbinlog pipeline: %w", err)
}

line := strings.TrimSpace(string(output))
Comment thread cmd/internal/db/db.go
Comment on lines +419 to +442
func (d *DB) ChangeReplicationSourceRelay(ctx context.Context, relayLogFile string, relayLogPos int, channel string) error {
_, err := d.db.ExecContext(ctx, fmt.Sprintf(
"CHANGE REPLICATION SOURCE TO RELAY_LOG_FILE='%s', RELAY_LOG_POS=%d, SOURCE_HOST='dummy'%s",
relayLogFile, relayLogPos, forChannelClause(channel)))
return errors.Wrap(err, "change replication source to relay log")
}

// ChangeReplicationFilterIgnoreDB sets REPLICATE_IGNORE_DB on the given
// channel so the SQL thread skips events whose default database is in dbs.
// Database names are emitted unquoted as required by CHANGE REPLICATION FILTER.
func (d *DB) ChangeReplicationFilterIgnoreDB(ctx context.Context, dbs []string, channel string) error {
if len(dbs) == 0 {
return errors.New("no databases provided")
}
_, err := d.db.ExecContext(ctx, fmt.Sprintf(
"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (%s)%s",
strings.Join(dbs, ", "), forChannelClause(channel)))
return errors.Wrap(err, "change replication filter")
}

func (d *DB) StartReplicaUntilGTID(ctx context.Context, gtid string, channel string) error {
_, err := d.db.ExecContext(ctx, fmt.Sprintf(
"START REPLICA SQL_THREAD UNTIL SQL_AFTER_GTIDS='%s'%s", gtid, forChannelClause(channel)))
return errors.Wrap(err, "start replica until GTID")
# date: "2024-11-18T11:10:48Z"
# force: false
# gtid: a3e5ff70-83e2-11ef-8e57-7a62caf7e1e3:1-36
# method: ""
Comment thread cmd/pitr/main_test.go
Comment on lines +132 to +141
path := filepath.Join(dir, fmt.Sprintf("myhost-relay-bin.%06d", i))
data, err := os.ReadFile(path)
require.NoErrorf(t, err, "placeholder %d must exist", i)
assert.Equalf(t, wantMagic, data, "placeholder %d must contain only the binlog magic", i)
}

indexData, err := os.ReadFile(filepath.Join(dir, "myhost-relay-bin.index"))
require.NoError(t, err)
assert.Equal(t,
"./myhost-relay-bin.000001\n./myhost-relay-bin.000002\n./myhost-relay-bin.000003\n",
@JNKPercona

Copy link
Copy Markdown
Collaborator
Test Name Result Time
async-ignore-annotations-8-4 passed 00:00:00
async-global-metadata-8-4 passed 00:00:00
async-upgrade-8-0 passed 00:00:00
async-upgrade-8-4 passed 00:00:00
auto-config-8-4 passed 00:24:48
config-8-4 passed 00:20:53
config-router-8-0 passed 00:00:00
config-router-8-4 passed 00:00:00
demand-backup-8-0 passed 00:19:58
demand-backup-8-4 passed 00:00:00
gr-pitr-minio-8-4 passed 00:00:00
gr-pitr-one-pod-8-4 passed 00:00:00
async-pitr-minio-8-4 passed 00:25:26
demand-backup-cloud-8-4 passed 00:00:00
demand-backup-retry-8-4 passed 00:00:00
demand-backup-incremental-8-0 passed 00:00:00
demand-backup-incremental-8-4 passed 00:00:00
async-data-at-rest-encryption-8-0 passed 00:14:18
async-data-at-rest-encryption-8-4 passed 00:00:00
gr-global-metadata-8-4 passed 00:00:00
gr-data-at-rest-encryption-8-0 passed 00:00:00
gr-data-at-rest-encryption-8-4 passed 00:00:00
gr-demand-backup-8-4 passed 00:00:00
gr-demand-backup-cloud-8-4 passed 00:00:00
gr-demand-backup-haproxy-8-4 passed 00:00:00
gr-demand-backup-incremental-8-0 passed 00:00:00
gr-demand-backup-incremental-8-4 passed 00:00:00
gr-finalizer-8-4 passed 00:00:00
gr-haproxy-8-0 passed 00:00:00
gr-haproxy-8-4 passed 00:00:00
gr-ignore-annotations-8-4 passed 00:00:00
gr-init-deploy-8-0 passed 00:00:00
gr-init-deploy-8-4 passed 00:00:00
gr-one-pod-8-4 passed 00:00:00
gr-recreate-8-4 passed 00:00:00
gr-scaling-8-4 passed 00:00:00
gr-scheduled-backup-8-4 passed 00:00:00
gr-scheduled-backup-incremental-8-4 passed 00:00:00
gr-security-context-8-4 passed 00:00:00
gr-self-healing-8-4 passed 00:00:00
gr-tls-cert-manager-8-4 passed 00:00:00
gr-users-8-4 passed 00:00:00
gr-upgrade-8-0 passed 00:00:00
gr-upgrade-8-4 passed 00:00:00
haproxy-8-0 passed 00:00:00
haproxy-8-4 passed 00:00:00
init-deploy-8-0 passed 00:00:00
init-deploy-8-4 passed 00:00:00
limits-8-4 passed 00:00:00
monitoring-8-4 passed 00:00:00
one-pod-8-0 passed 00:00:00
one-pod-8-4 passed 00:00:00
operator-self-healing-8-4 passed 00:00:00
pvc-resize-8-4 passed 00:00:00
recreate-8-4 passed 00:00:00
scaling-8-4 passed 00:00:00
scheduled-backup-8-0 passed 00:00:00
scheduled-backup-8-4 passed 00:00:00
scheduled-backup-incremental-8-0 passed 00:00:00
scheduled-backup-incremental-8-4 passed 00:00:00
service-per-pod-8-4 passed 00:00:00
sidecars-8-4 passed 00:00:00
smart-update-8-4 passed 00:00:00
storage-8-4 passed 00:00:00
telemetry-8-4 passed 00:00:00
tls-cert-manager-8-4 passed 00:00:00
users-8-0 passed 00:00:00
users-8-4 passed 00:00:00
version-service-8-4 passed 00:00:00
Summary Value
Tests Run 69/69
Job Duration 00:57:48
Total Test Time 01:45:26

commit: 1a8dec7
image: perconalab/percona-server-mysql-operator:PR-1334-1a8dec7e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL 1000+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants