From 3cdd76b9b486ecd1297714c4aacfbb380b81661f Mon Sep 17 00:00:00 2001 From: RidRisR <79858083+RidRisR@users.noreply.github.com> Date: Thu, 28 May 2026 09:41:13 +0200 Subject: [PATCH 1/8] Fix compact backup job labels --- pkg/apis/label/label.go | 24 +++++++++++++ pkg/apis/pingcap/v1alpha1/compact_backup.go | 26 ++++++++++++++ .../compact_backup_controller.go | 3 +- .../compact_backup_controller_sharded_test.go | 35 +++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 pkg/apis/pingcap/v1alpha1/compact_backup.go diff --git a/pkg/apis/label/label.go b/pkg/apis/label/label.go index 75c62fc87d..26e1167e26 100644 --- a/pkg/apis/label/label.go +++ b/pkg/apis/label/label.go @@ -59,6 +59,9 @@ const ( // BackupLabelKey is backup key BackupLabelKey string = "tidb.pingcap.com/backup" + // CompactLabelKey is compact backup key + CompactLabelKey string = "tidb.pingcap.com/compact" + // RestoreLabelKey is restore key RestoreLabelKey string = "tidb.pingcap.com/restore" // RestoreWarmUpLabelKey defines which pod the restore warms up @@ -224,6 +227,8 @@ const ( RestoreWarmUpJobLabelVal string = "warmup" // BackupJobLabelVal is backup job label value BackupJobLabelVal string = "backup" + // CompactJobLabelVal is compact backup job label value + CompactJobLabelVal string = "compact-backup" // BackupScheduleJobLabelVal is backup schedule job label value BackupScheduleJobLabelVal string = "backup-schedule" // InitJobLabelVal is TiDB initializer job label value @@ -305,6 +310,14 @@ func NewBackup() Label { } } +// NewCompactBackup initialize a new Label for Jobs of compact backup +func NewCompactBackup() Label { + return Label{ + NameLabelKey: CompactJobLabelVal, + ManagedByLabelKey: "compact-backup-operator", + } +} + // NewRestore initialize a new Label for Jobs of restore func NewRestore() Label { return Label{ @@ -419,6 +432,11 @@ func (l Label) BackupJob() Label { return l.Component(BackupJobLabelVal) } +// CompactJob assigns compact backup to component key in label +func (l Label) CompactJob() Label { + return l.Component(CompactJobLabelVal) +} + // RestoreJob assigns restore to component key in label func (l Label) RestoreJob() Label { return l.Component(RestoreJobLabelVal) @@ -434,6 +452,12 @@ func (l Label) Backup(val string) Label { return l } +// Compact assigns specific value to compact backup key in label +func (l Label) Compact(val string) Label { + l[CompactLabelKey] = val + return l +} + // BackupSchedule assigns specific value to backup schedule key in label func (l Label) BackupSchedule(val string) Label { l[BackupScheduleLabelKey] = val diff --git a/pkg/apis/pingcap/v1alpha1/compact_backup.go b/pkg/apis/pingcap/v1alpha1/compact_backup.go new file mode 100644 index 0000000000..af4e9d6166 --- /dev/null +++ b/pkg/apis/pingcap/v1alpha1/compact_backup.go @@ -0,0 +1,26 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import "github.com/pingcap/tidb-operator/pkg/apis/label" + +// GetInstanceName return the compact backup instance name. +func (cb *CompactBackup) GetInstanceName() string { + if cb.Labels != nil { + if v, ok := cb.Labels[label.InstanceLabelKey]; ok { + return v + } + } + return cb.Name +} diff --git a/pkg/controller/compactbackup/compact_backup_controller.go b/pkg/controller/compactbackup/compact_backup_controller.go index 9096aacb16..d77248ebcd 100644 --- a/pkg/controller/compactbackup/compact_backup_controller.go +++ b/pkg/controller/compactbackup/compact_backup_controller.go @@ -400,8 +400,7 @@ func (c *Controller) makeCompactJob(compact *v1alpha1.CompactBackup) (*batchv1.J } klog.Infof("compact %s/%s use br image %s and tikv image %s", ns, name, brImage, tikvImage) - //TODO: (Ris)What is the instance here? - jobLabels := util.CombineStringMap(label.NewBackup().Instance("Compact-Backup").BackupJob().Backup(name), compact.Labels) + jobLabels := util.CombineStringMap(label.NewCompactBackup().Instance(compact.GetInstanceName()).CompactJob().Compact(name), compact.Labels) podLabels := jobLabels jobAnnotations := compact.Annotations podAnnotations := jobAnnotations diff --git a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go index 3e427655ed..f85a1634d9 100644 --- a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go +++ b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go @@ -69,6 +69,41 @@ func TestMakeCompactJobDefaultModeUnchanged(t *testing.T) { } } +func TestMakeCompactJobUsesCompactBackupLabels(t *testing.T) { + c := newTestController(t) + compact := newCompactBackupForTest() + compact.Labels = map[string]string{ + "app.kubernetes.io/instance": "daily-log-backup", + } + + job, reason, err := c.makeCompactJob(compact) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if reason != "" { + t.Fatalf("expected empty reason, got %q", reason) + } + + expectedLabels := map[string]string{ + "app.kubernetes.io/name": "compact-backup", + "app.kubernetes.io/managed-by": "compact-backup-operator", + "app.kubernetes.io/instance": "daily-log-backup", + "app.kubernetes.io/component": "compact-backup", + "tidb.pingcap.com/compact": compact.Name, + } + for k, v := range expectedLabels { + if job.Labels[k] != v { + t.Fatalf("expected job label %s=%q, got %q", k, v, job.Labels[k]) + } + if job.Spec.Template.Labels[k] != v { + t.Fatalf("expected pod template label %s=%q, got %q", k, v, job.Spec.Template.Labels[k]) + } + } + if _, ok := job.Labels["tidb.pingcap.com/backup"]; ok { + t.Fatalf("compact job should not use backup label key, got labels %v", job.Labels) + } +} + func TestMakeCompactJobShardedMode(t *testing.T) { c := newTestController(t) compact := newCompactBackupForTest() From f037beccec4522cf6b6c65f696df08595922b60a Mon Sep 17 00:00:00 2001 From: RidRisR <79858083+RidRisR@users.noreply.github.com> Date: Fri, 29 May 2026 07:17:26 +0200 Subject: [PATCH 2/8] feat: configure sharded compact tikv-ctl args --- cmd/backup-manager/app/compact/manager.go | 5 +- .../app/compact/manager_sharded_test.go | 66 ++++--- .../app/compact/options/options.go | 36 ++-- .../compact/options/options_sharded_test.go | 168 ++++++++++++++---- docs/api-references/docs.md | 50 ++++++ manifests/crd.yaml | 8 + .../crd/v1/pingcap.com_backupschedules.yaml | 4 + .../crd/v1/pingcap.com_compactbackups.yaml | 4 + .../pingcap/v1alpha1/openapi_generated.go | 14 ++ pkg/apis/pingcap/v1alpha1/types.go | 7 + .../compact_backup_controller.go | 15 ++ .../compact_backup_controller_sharded_test.go | 63 +++++++ 12 files changed, 366 insertions(+), 74 deletions(-) diff --git a/cmd/backup-manager/app/compact/manager.go b/cmd/backup-manager/app/compact/manager.go index 85de0b4d75..401f65f2de 100644 --- a/cmd/backup-manager/app/compact/manager.go +++ b/cmd/backup-manager/app/compact/manager.go @@ -190,6 +190,9 @@ func (cm *Manager) buildCompactArgs(base64Storage string) []string { "-N", strconv.FormatUint(cm.options.Concurrency, 10), } + if cm.options.Name != "" { + args = append(args, "--name", cm.options.Name) + } if cm.options.Sharded { return cm.buildShardedCompactArgs(args) @@ -208,7 +211,7 @@ func (cm *Manager) buildShardedCompactArgs(args []string) []string { args = append(args, "--cal-shift-ts", "--physical-file-cache-capacity", - "150G", + cm.options.PhysicalFileCacheCapacity, ) // When the CR sets EndTs explicitly, honor it as a hard upper bound via diff --git a/cmd/backup-manager/app/compact/manager_sharded_test.go b/cmd/backup-manager/app/compact/manager_sharded_test.go index 2da2cf0ee3..39d6167497 100644 --- a/cmd/backup-manager/app/compact/manager_sharded_test.go +++ b/cmd/backup-manager/app/compact/manager_sharded_test.go @@ -56,12 +56,13 @@ func TestBuildCompactArgsShardedMode(t *testing.T) { manager := &Manager{ compact: &v1alpha1.CompactBackup{}, options: options.CompactOpts{ - FromTS: 11, - UntilTS: 22, - Concurrency: 4, - Sharded: true, - ShardIndex: 1, - ShardCount: 3, + FromTS: 11, + UntilTS: 22, + Concurrency: 4, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 1, + ShardCount: 3, }, } @@ -74,7 +75,7 @@ func TestBuildCompactArgsShardedMode(t *testing.T) { "--from", "11", "-N", "4", "--cal-shift-ts", - "--physical-file-cache-capacity", "150G", + "--physical-file-cache-capacity", "200G", "--until", "22", "--shard", "2/3", "--minimal-compaction-size", "0", @@ -87,12 +88,13 @@ func TestBuildCompactArgsCRRModeShardedUsesCheckpointPrefix(t *testing.T) { manager := &Manager{ compact: &v1alpha1.CompactBackup{}, options: options.CompactOpts{ - FromTS: 11, - UntilTS: 0, - Concurrency: 4, - Sharded: true, - ShardIndex: 1, - ShardCount: 3, + FromTS: 11, + UntilTS: 0, + Concurrency: 4, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 1, + ShardCount: 3, }, } @@ -105,7 +107,7 @@ func TestBuildCompactArgsCRRModeShardedUsesCheckpointPrefix(t *testing.T) { "--from", "11", "-N", "4", "--cal-shift-ts", - "--physical-file-cache-capacity", "150G", + "--physical-file-cache-capacity", "200G", "--crr-checkpoint-prefix", "crr-checkpoint", "--shard", "2/3", "--minimal-compaction-size", "0", @@ -115,20 +117,37 @@ func TestBuildCompactArgsCRRModeShardedUsesCheckpointPrefix(t *testing.T) { } func TestBuildCompactArgsShardedModeConvertsKubernetesIndexToOneBasedShard(t *testing.T) { + manager := &Manager{ + compact: &v1alpha1.CompactBackup{}, + options: options.CompactOpts{ + FromTS: 11, + UntilTS: 22, + Concurrency: 4, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 0, + ShardCount: 3, + }, + } + + args := manager.buildCompactArgs("storage-base64") + assertStringSliceContainsPair(t, args, "--shard", "1/3") +} + +func TestBuildCompactArgsPassesNameWhenConfigured(t *testing.T) { manager := &Manager{ compact: &v1alpha1.CompactBackup{}, options: options.CompactOpts{ FromTS: 11, UntilTS: 22, + Name: "compact-task-a", Concurrency: 4, - Sharded: true, - ShardIndex: 0, - ShardCount: 3, }, } args := manager.buildCompactArgs("storage-base64") - assertStringSliceContainsPair(t, args, "--shard", "1/3") + + assertStringSliceContainsPair(t, args, "--name", "compact-task-a") } func TestSanitizeCompactCommandArgsRedactsStorageBase64Value(t *testing.T) { @@ -271,11 +290,12 @@ func newShardedCompactBackupForManagerTest() *v1alpha1.CompactBackup { Namespace: "default", }, Spec: v1alpha1.CompactSpec{ - StartTs: "400036290571534337", - EndTs: "400036290571534338", - Concurrency: 4, - Mode: v1alpha1.CompactModeSharded, - ShardCount: &shardCount, + StartTs: "400036290571534337", + EndTs: "400036290571534338", + Concurrency: 4, + Mode: v1alpha1.CompactModeSharded, + ShardCount: &shardCount, + PhysicalFileCacheCapacity: "200G", }, } } diff --git a/cmd/backup-manager/app/compact/options/options.go b/cmd/backup-manager/app/compact/options/options.go index dbd7bc5fc8..fe34054a26 100644 --- a/cmd/backup-manager/app/compact/options/options.go +++ b/cmd/backup-manager/app/compact/options/options.go @@ -18,10 +18,12 @@ import ( "math" "os" "strconv" + "strings" "github.com/pingcap/errors" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/apis/util/config" + "k8s.io/apimachinery/pkg/api/resource" ) const ( @@ -30,16 +32,17 @@ const ( ) type CompactOpts struct { - FromTS uint64 - UntilTS uint64 - Name string - Concurrency uint64 - ShardIndex int - ShardCount int - Sharded bool - Namespace string `json:"namespace"` - ResourceName string `json:"resourceName"` - TikvVersion string `json:"tikvVersion"` + FromTS uint64 + UntilTS uint64 + Name string + Concurrency uint64 + PhysicalFileCacheCapacity string + ShardIndex int + ShardCount int + Sharded bool + Namespace string `json:"namespace"` + ResourceName string `json:"resourceName"` + TikvVersion string `json:"tikvVersion"` } func ParseCompactOptions(compact *v1alpha1.CompactBackup, opts *CompactOpts) error { @@ -55,8 +58,9 @@ func ParseCompactOptions(compact *v1alpha1.CompactBackup, opts *CompactOpts) err opts.FromTS = startTs opts.UntilTS = endTs - opts.Name = compact.Name + opts.Name = strings.TrimSpace(compact.Spec.Name) opts.Concurrency = uint64(compact.Spec.Concurrency) + opts.PhysicalFileCacheCapacity = strings.TrimSpace(compact.Spec.PhysicalFileCacheCapacity) opts.Sharded = compact.Spec.Mode == v1alpha1.CompactModeSharded opts.ShardIndex = 0 opts.ShardCount = 0 @@ -95,6 +99,16 @@ func (c *CompactOpts) Verify() error { return errors.Errorf("concurrency %d must be greater than 0", c.Concurrency) } if c.Sharded { + if c.PhysicalFileCacheCapacity == "" { + return errors.New("physicalFileCacheCapacity must be set when mode is sharded") + } + capacity, err := resource.ParseQuantity(c.PhysicalFileCacheCapacity) + if err != nil { + return errors.Annotatef(err, "invalid physicalFileCacheCapacity %q", c.PhysicalFileCacheCapacity) + } + if capacity.Sign() <= 0 { + return errors.New("physicalFileCacheCapacity must be greater than 0") + } if c.ShardCount <= 0 { return errors.Errorf("shard-count %d must be greater than 0", c.ShardCount) } diff --git a/cmd/backup-manager/app/compact/options/options_sharded_test.go b/cmd/backup-manager/app/compact/options/options_sharded_test.go index aa1b259fa7..d43364c5b5 100644 --- a/cmd/backup-manager/app/compact/options/options_sharded_test.go +++ b/cmd/backup-manager/app/compact/options/options_sharded_test.go @@ -33,11 +33,13 @@ func TestParseCompactOptionsShardedFields(t *testing.T) { Name: "compact-sharded", }, Spec: v1alpha1.CompactSpec{ - StartTs: "400036290571534337", - EndTs: "400036290571534338", - Concurrency: 8, - Mode: v1alpha1.CompactModeSharded, - ShardCount: &shardCount, + StartTs: "400036290571534337", + EndTs: "400036290571534338", + Concurrency: 8, + Mode: v1alpha1.CompactModeSharded, + ShardCount: &shardCount, + PhysicalFileCacheCapacity: "200G", + Name: "compact-task-a", }, } @@ -54,6 +56,12 @@ func TestParseCompactOptionsShardedFields(t *testing.T) { if opts.ShardIndex != 2 { t.Fatalf("expected shard index 2, got %d", opts.ShardIndex) } + if opts.PhysicalFileCacheCapacity != "200G" { + t.Fatalf("expected physical file cache capacity %q, got %q", "200G", opts.PhysicalFileCacheCapacity) + } + if opts.Name != "compact-task-a" { + t.Fatalf("expected name %q, got %q", "compact-task-a", opts.Name) + } } func TestParseCompactOptionsShardedFieldsRequireRuntimeIndex(t *testing.T) { @@ -77,11 +85,12 @@ func TestParseCompactOptionsShardedFieldsRequireRuntimeIndex(t *testing.T) { Name: "compact-sharded-missing-index", }, Spec: v1alpha1.CompactSpec{ - StartTs: "400036290571534337", - EndTs: "400036290571534338", - Concurrency: 8, - Mode: v1alpha1.CompactModeSharded, - ShardCount: &shardCount, + StartTs: "400036290571534337", + EndTs: "400036290571534338", + Concurrency: 8, + Mode: v1alpha1.CompactModeSharded, + ShardCount: &shardCount, + PhysicalFileCacheCapacity: "200G", }, } @@ -123,11 +132,12 @@ func TestParseCompactOptionsShardedFieldsRejectInvalidRuntimeIndex(t *testing.T) Name: "compact-sharded-invalid-index", }, Spec: v1alpha1.CompactSpec{ - StartTs: "400036290571534337", - EndTs: "400036290571534338", - Concurrency: 8, - Mode: v1alpha1.CompactModeSharded, - ShardCount: &shardCount, + StartTs: "400036290571534337", + EndTs: "400036290571534338", + Concurrency: 8, + Mode: v1alpha1.CompactModeSharded, + ShardCount: &shardCount, + PhysicalFileCacheCapacity: "200G", }, } @@ -173,6 +183,82 @@ func TestParseCompactOptionsDefaultModeClearsShardedFields(t *testing.T) { if opts.ShardIndex != 0 { t.Fatalf("expected shard index to be cleared, got %d", opts.ShardIndex) } + if opts.Name != "" { + t.Fatalf("expected metadata name not to be used as compact --name, got %q", opts.Name) + } +} + +func TestParseCompactOptionsShardedRequiresPhysicalFileCacheCapacity(t *testing.T) { + shardCount := int32(4) + t.Setenv("JOB_COMPLETION_INDEX", "2") + opts := &CompactOpts{} + + compact := &v1alpha1.CompactBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "compact-sharded", + }, + Spec: v1alpha1.CompactSpec{ + StartTs: "400036290571534337", + EndTs: "400036290571534338", + Concurrency: 8, + Mode: v1alpha1.CompactModeSharded, + ShardCount: &shardCount, + }, + } + + err := ParseCompactOptions(compact, opts) + if err == nil { + t.Fatal("expected ParseCompactOptions to fail without physicalFileCacheCapacity") + } + if !strings.Contains(err.Error(), "physicalFileCacheCapacity") { + t.Fatalf("expected physicalFileCacheCapacity validation error, got %v", err) + } +} + +func TestCompactOptsVerifyRejectsInvalidPhysicalFileCacheCapacity(t *testing.T) { + testCases := []struct { + name string + capacity string + wantErr string + }{ + { + name: "invalid quantity", + capacity: "150GB", + wantErr: "invalid physicalFileCacheCapacity", + }, + { + name: "zero quantity", + capacity: "0", + wantErr: "must be greater than 0", + }, + { + name: "negative quantity", + capacity: "-1G", + wantErr: "must be greater than 0", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + opts := CompactOpts{ + FromTS: 1, + UntilTS: 2, + Concurrency: 1, + PhysicalFileCacheCapacity: tc.capacity, + Sharded: true, + ShardIndex: 0, + ShardCount: 3, + } + + err := opts.Verify() + if err == nil { + t.Fatal("expected Verify to fail") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("expected error containing %q, got %v", tc.wantErr, err) + } + }) + } } func TestCompactOptsVerifyRejectsInvalidKubernetesShardIndex(t *testing.T) { @@ -184,36 +270,39 @@ func TestCompactOptsVerifyRejectsInvalidKubernetesShardIndex(t *testing.T) { { name: "non-positive shard count", opts: CompactOpts{ - FromTS: 1, - UntilTS: 2, - Concurrency: 1, - Sharded: true, - ShardIndex: 0, - ShardCount: 0, + FromTS: 1, + UntilTS: 2, + Concurrency: 1, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 0, + ShardCount: 0, }, wantErr: "shard-count", }, { name: "negative shard index", opts: CompactOpts{ - FromTS: 1, - UntilTS: 2, - Concurrency: 1, - Sharded: true, - ShardIndex: -1, - ShardCount: 3, + FromTS: 1, + UntilTS: 2, + Concurrency: 1, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: -1, + ShardCount: 3, }, wantErr: "kubernetes shard-index", }, { name: "out of range shard index", opts: CompactOpts{ - FromTS: 1, - UntilTS: 2, - Concurrency: 1, - Sharded: true, - ShardIndex: 3, - ShardCount: 3, + FromTS: 1, + UntilTS: 2, + Concurrency: 1, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 3, + ShardCount: 3, }, wantErr: "kubernetes shard-index", }, @@ -234,12 +323,13 @@ func TestCompactOptsVerifyRejectsInvalidKubernetesShardIndex(t *testing.T) { func TestCompactOptsVerifyAllowsUnsetUntilTSOnlyWhenSharded(t *testing.T) { sharded := CompactOpts{ - FromTS: 1, - UntilTS: 0, - Concurrency: 1, - Sharded: true, - ShardIndex: 0, - ShardCount: 3, + FromTS: 1, + UntilTS: 0, + Concurrency: 1, + PhysicalFileCacheCapacity: "200G", + Sharded: true, + ShardIndex: 0, + ShardCount: 3, } if err := sharded.Verify(); err != nil { t.Fatalf("expected sharded unset UntilTS to be accepted, got %v", err) diff --git a/docs/api-references/docs.md b/docs/api-references/docs.md index 4b5ecefc93..26a43bd1e4 100644 --- a/docs/api-references/docs.md +++ b/docs/api-references/docs.md @@ -5442,6 +5442,31 @@ int32
physicalFileCacheCapacity
+
+string
+
+PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as –physical-file-cache-capacity. +It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and is required when mode is sharded.
+name
+
+string
+
+Name is passed to tikv-ctl compact-log-backup as –name when configured.
+env
@@ -5840,6 +5865,31 @@ int32
physicalFileCacheCapacity
+
+string
+
+PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as –physical-file-cache-capacity. +It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and is required when mode is sharded.
+name
+
+string
+
+Name is passed to tikv-ctl compact-log-backup as –name when configured.
+env
diff --git a/manifests/crd.yaml b/manifests/crd.yaml
index 9175dd8c22..bd39d2b32c 100644
--- a/manifests/crd.yaml
+++ b/manifests/crd.yaml
@@ -7108,6 +7108,10 @@ spec:
- ""
- sharded
type: string
+ name:
+ type: string
+ physicalFileCacheCapacity:
+ type: string
podSecurityContext:
properties:
fsGroup:
@@ -12624,6 +12628,10 @@ spec:
- ""
- sharded
type: string
+ name:
+ type: string
+ physicalFileCacheCapacity:
+ type: string
podSecurityContext:
properties:
fsGroup:
diff --git a/manifests/crd/v1/pingcap.com_backupschedules.yaml b/manifests/crd/v1/pingcap.com_backupschedules.yaml
index ed8f093461..2fb10add5a 100644
--- a/manifests/crd/v1/pingcap.com_backupschedules.yaml
+++ b/manifests/crd/v1/pingcap.com_backupschedules.yaml
@@ -4541,6 +4541,10 @@ spec:
- ""
- sharded
type: string
+ name:
+ type: string
+ physicalFileCacheCapacity:
+ type: string
podSecurityContext:
properties:
fsGroup:
diff --git a/manifests/crd/v1/pingcap.com_compactbackups.yaml b/manifests/crd/v1/pingcap.com_compactbackups.yaml
index 7a1cff6aaf..45caa0eade 100644
--- a/manifests/crd/v1/pingcap.com_compactbackups.yaml
+++ b/manifests/crd/v1/pingcap.com_compactbackups.yaml
@@ -2134,6 +2134,10 @@ spec:
- ""
- sharded
type: string
+ name:
+ type: string
+ physicalFileCacheCapacity:
+ type: string
podSecurityContext:
properties:
fsGroup:
diff --git a/pkg/apis/pingcap/v1alpha1/openapi_generated.go b/pkg/apis/pingcap/v1alpha1/openapi_generated.go
index 024fca61d1..f6e7daf30a 100644
--- a/pkg/apis/pingcap/v1alpha1/openapi_generated.go
+++ b/pkg/apis/pingcap/v1alpha1/openapi_generated.go
@@ -1614,6 +1614,20 @@ func schema_pkg_apis_pingcap_v1alpha1_CompactSpec(ref common.ReferenceCallback)
Format: "int32",
},
},
+ "physicalFileCacheCapacity": {
+ SchemaProps: spec.SchemaProps{
+ Description: "PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as --physical-file-cache-capacity. It uses Kubernetes quantity format, e.g. \"150G\" or \"150Gi\", and is required when mode is sharded.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Name is passed to tikv-ctl compact-log-backup as --name when configured.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
"env": {
SchemaProps: spec.SchemaProps{
Description: "List of environment variables to set in the container, like v1.Container.Env. Note that the following builtin env vars will be overwritten by values set here - S3_PROVIDER - S3_ENDPOINT - AWS_REGION - AWS_ACL - AWS_STORAGE_CLASS - AWS_DEFAULT_REGION - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - GCS_PROJECT_ID - GCS_OBJECT_ACL - GCS_BUCKET_ACL - GCS_LOCATION - GCS_STORAGE_CLASS - GCS_SERVICE_ACCOUNT_JSON_KEY - BR_LOG_TO_TERM",
diff --git a/pkg/apis/pingcap/v1alpha1/types.go b/pkg/apis/pingcap/v1alpha1/types.go
index 60fd5cc1f5..66bfd92221 100644
--- a/pkg/apis/pingcap/v1alpha1/types.go
+++ b/pkg/apis/pingcap/v1alpha1/types.go
@@ -3965,6 +3965,13 @@ type CompactSpec struct {
// +kubebuilder:validation:Minimum=1
// +optional
ShardCount *int32 `json:"shardCount,omitempty"`
+ // PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as --physical-file-cache-capacity.
+ // It uses Kubernetes quantity format, e.g. "150G" or "150Gi", and is required when mode is sharded.
+ // +optional
+ PhysicalFileCacheCapacity string `json:"physicalFileCacheCapacity,omitempty"`
+ // Name is passed to tikv-ctl compact-log-backup as --name when configured.
+ // +optional
+ Name string `json:"name,omitempty"`
// List of environment variables to set in the container, like v1.Container.Env.
// Note that the following builtin env vars will be overwritten by values set here
// - S3_PROVIDER
diff --git a/pkg/controller/compactbackup/compact_backup_controller.go b/pkg/controller/compactbackup/compact_backup_controller.go
index d77248ebcd..3e8f8df520 100644
--- a/pkg/controller/compactbackup/compact_backup_controller.go
+++ b/pkg/controller/compactbackup/compact_backup_controller.go
@@ -17,6 +17,7 @@ import (
"context"
"fmt"
"math"
+ "strings"
"time"
perrors "github.com/pingcap/errors"
@@ -30,6 +31,7 @@ import (
"github.com/pingcap/tidb-operator/pkg/util"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
@@ -606,6 +608,19 @@ func (c *Controller) validate(compact *v1alpha1.CompactBackup) error {
if spec.Mode == v1alpha1.CompactModeSharded && (spec.ShardCount == nil || *spec.ShardCount < 1) {
return perrors.NewNoStackError("shardCount must be greater than or equal to 1 when mode is sharded")
}
+ if spec.Mode == v1alpha1.CompactModeSharded {
+ physicalFileCacheCapacity := strings.TrimSpace(spec.PhysicalFileCacheCapacity)
+ if physicalFileCacheCapacity == "" {
+ return perrors.NewNoStackError("physicalFileCacheCapacity must be set when mode is sharded")
+ }
+ capacity, err := resource.ParseQuantity(physicalFileCacheCapacity)
+ if err != nil {
+ return perrors.NewNoStackError(fmt.Sprintf("invalid physicalFileCacheCapacity %q: %v", physicalFileCacheCapacity, err))
+ }
+ if capacity.Sign() <= 0 {
+ return perrors.NewNoStackError("physicalFileCacheCapacity must be greater than 0")
+ }
+ }
if spec.Mode != v1alpha1.CompactModeSharded && spec.ShardCount != nil {
return perrors.NewNoStackError("shardCount can only be set when mode is sharded")
}
diff --git a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
index f85a1634d9..4e51e5b4d2 100644
--- a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
+++ b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
@@ -145,6 +145,7 @@ func TestValidateShardedModeRequiresPositiveShardCount(t *testing.T) {
c := newTestController(t)
compact := newCompactBackupForTest()
compact.Spec.Mode = v1alpha1.CompactModeSharded
+ compact.Spec.PhysicalFileCacheCapacity = "200G"
err := c.validate(compact)
if err == nil {
@@ -155,6 +156,65 @@ func TestValidateShardedModeRequiresPositiveShardCount(t *testing.T) {
}
}
+func TestValidateShardedModeRequiresPhysicalFileCacheCapacity(t *testing.T) {
+ c := newTestController(t)
+ compact := newCompactBackupForTest()
+ shardCount := int32(3)
+ compact.Spec.Mode = v1alpha1.CompactModeSharded
+ compact.Spec.ShardCount = &shardCount
+
+ err := c.validate(compact)
+ if err == nil {
+ t.Fatal("expected validation error, got nil")
+ }
+ if !strings.Contains(err.Error(), "physicalFileCacheCapacity") {
+ t.Fatalf("expected physicalFileCacheCapacity validation error, got %v", err)
+ }
+}
+
+func TestValidateShardedModeRejectsInvalidPhysicalFileCacheCapacity(t *testing.T) {
+ testCases := []struct {
+ name string
+ capacity string
+ wantErr string
+ }{
+ {
+ name: "invalid quantity",
+ capacity: "150GB",
+ wantErr: "invalid physicalFileCacheCapacity",
+ },
+ {
+ name: "zero quantity",
+ capacity: "0",
+ wantErr: "must be greater than 0",
+ },
+ {
+ name: "negative quantity",
+ capacity: "-1G",
+ wantErr: "must be greater than 0",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ c := newTestController(t)
+ compact := newCompactBackupForTest()
+ shardCount := int32(3)
+ compact.Spec.Mode = v1alpha1.CompactModeSharded
+ compact.Spec.ShardCount = &shardCount
+ compact.Spec.PhysicalFileCacheCapacity = tc.capacity
+
+ err := c.validate(compact)
+ if err == nil {
+ t.Fatal("expected validation error, got nil")
+ }
+ if !strings.Contains(err.Error(), tc.wantErr) {
+ t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
+ }
+ })
+ }
+}
+
func TestValidateRejectsShardCountWithoutShardedMode(t *testing.T) {
c := newTestController(t)
compact := newCompactBackupForTest()
@@ -177,6 +237,7 @@ func TestValidateAllowsEmptyEndTsOnlyForShardedCCRCheckpointMode(t *testing.T) {
shardCount := int32(3)
compact.Spec.Mode = v1alpha1.CompactModeSharded
compact.Spec.ShardCount = &shardCount
+ compact.Spec.PhysicalFileCacheCapacity = "200G"
compact.Spec.EndTs = ""
err := c.validate(compact)
@@ -207,6 +268,7 @@ func TestSyncShardedModeRequiresSupportedK8sVersion(t *testing.T) {
shardCount := int32(2)
compact.Spec.Mode = v1alpha1.CompactModeSharded
compact.Spec.ShardCount = &shardCount
+ compact.Spec.PhysicalFileCacheCapacity = "200G"
fakeDiscovery := c.deps.KubeClientset.Discovery().(*fakediscovery.FakeDiscovery)
fakeDiscovery.FakedServerVersion = &version.Info{Major: "1", Minor: "28"}
@@ -250,6 +312,7 @@ func TestSyncShardedModeRequeuesOnDiscoveryError(t *testing.T) {
shardCount := int32(2)
compact.Spec.Mode = v1alpha1.CompactModeSharded
compact.Spec.ShardCount = &shardCount
+ compact.Spec.PhysicalFileCacheCapacity = "200G"
fakeDiscovery := c.deps.KubeClientset.Discovery().(*fakediscovery.FakeDiscovery)
fakeDiscovery.PrependReactor("get", "version", func(action k8stesting.Action) (bool, runtime.Object, error) {
From 21500ada79ea6ae62ced59102183842ee14ca752 Mon Sep 17 00:00:00 2001
From: RidRisR <79858083+RidRisR@users.noreply.github.com>
Date: Fri, 5 Jun 2026 10:36:29 +0200
Subject: [PATCH 3/8] Align compactbackup label value
---
pkg/apis/label/label.go | 2 +-
.../compactbackup/compact_backup_controller_sharded_test.go | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/pkg/apis/label/label.go b/pkg/apis/label/label.go
index 26e1167e26..93e85ccc0d 100644
--- a/pkg/apis/label/label.go
+++ b/pkg/apis/label/label.go
@@ -228,7 +228,7 @@ const (
// BackupJobLabelVal is backup job label value
BackupJobLabelVal string = "backup"
// CompactJobLabelVal is compact backup job label value
- CompactJobLabelVal string = "compact-backup"
+ CompactJobLabelVal string = "compactbackup"
// BackupScheduleJobLabelVal is backup schedule job label value
BackupScheduleJobLabelVal string = "backup-schedule"
// InitJobLabelVal is TiDB initializer job label value
diff --git a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
index 4e51e5b4d2..cf94eacacc 100644
--- a/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
+++ b/pkg/controller/compactbackup/compact_backup_controller_sharded_test.go
@@ -85,10 +85,10 @@ func TestMakeCompactJobUsesCompactBackupLabels(t *testing.T) {
}
expectedLabels := map[string]string{
- "app.kubernetes.io/name": "compact-backup",
+ "app.kubernetes.io/name": "compactbackup",
"app.kubernetes.io/managed-by": "compact-backup-operator",
"app.kubernetes.io/instance": "daily-log-backup",
- "app.kubernetes.io/component": "compact-backup",
+ "app.kubernetes.io/component": "compactbackup",
"tidb.pingcap.com/compact": compact.Name,
}
for k, v := range expectedLabels {
From 00c0cdb8d34939f5643777059a58021914b5ef7e Mon Sep 17 00:00:00 2001
From: RidRisR <79858083+RidRisR@users.noreply.github.com>
Date: Fri, 5 Jun 2026 11:05:14 +0200
Subject: [PATCH 4/8] Default compact backup cache capacity
---
.../app/compact/options/options.go | 10 ++++--
.../compact/options/options_sharded_test.go | 33 ++++++++++++-------
docs/api-references/docs.md | 4 +--
.../pingcap/v1alpha1/openapi_generated.go | 2 +-
pkg/apis/pingcap/v1alpha1/types.go | 2 +-
.../compact_backup_controller.go | 17 +++++-----
.../compact_backup_controller_sharded_test.go | 28 +++++++++-------
7 files changed, 58 insertions(+), 38 deletions(-)
diff --git a/cmd/backup-manager/app/compact/options/options.go b/cmd/backup-manager/app/compact/options/options.go
index fe34054a26..df0ec267fe 100644
--- a/cmd/backup-manager/app/compact/options/options.go
+++ b/cmd/backup-manager/app/compact/options/options.go
@@ -61,6 +61,9 @@ func ParseCompactOptions(compact *v1alpha1.CompactBackup, opts *CompactOpts) err
opts.Name = strings.TrimSpace(compact.Spec.Name)
opts.Concurrency = uint64(compact.Spec.Concurrency)
opts.PhysicalFileCacheCapacity = strings.TrimSpace(compact.Spec.PhysicalFileCacheCapacity)
+ if opts.PhysicalFileCacheCapacity == "" {
+ opts.PhysicalFileCacheCapacity = "0"
+ }
opts.Sharded = compact.Spec.Mode == v1alpha1.CompactModeSharded
opts.ShardIndex = 0
opts.ShardCount = 0
@@ -99,15 +102,16 @@ func (c *CompactOpts) Verify() error {
return errors.Errorf("concurrency %d must be greater than 0", c.Concurrency)
}
if c.Sharded {
+ c.PhysicalFileCacheCapacity = strings.TrimSpace(c.PhysicalFileCacheCapacity)
if c.PhysicalFileCacheCapacity == "" {
- return errors.New("physicalFileCacheCapacity must be set when mode is sharded")
+ c.PhysicalFileCacheCapacity = "0"
}
capacity, err := resource.ParseQuantity(c.PhysicalFileCacheCapacity)
if err != nil {
return errors.Annotatef(err, "invalid physicalFileCacheCapacity %q", c.PhysicalFileCacheCapacity)
}
- if capacity.Sign() <= 0 {
- return errors.New("physicalFileCacheCapacity must be greater than 0")
+ if capacity.Sign() < 0 {
+ return errors.New("physicalFileCacheCapacity must be greater than or equal to 0")
}
if c.ShardCount <= 0 {
return errors.Errorf("shard-count %d must be greater than 0", c.ShardCount)
diff --git a/cmd/backup-manager/app/compact/options/options_sharded_test.go b/cmd/backup-manager/app/compact/options/options_sharded_test.go
index d43364c5b5..ac2066a06e 100644
--- a/cmd/backup-manager/app/compact/options/options_sharded_test.go
+++ b/cmd/backup-manager/app/compact/options/options_sharded_test.go
@@ -188,7 +188,7 @@ func TestParseCompactOptionsDefaultModeClearsShardedFields(t *testing.T) {
}
}
-func TestParseCompactOptionsShardedRequiresPhysicalFileCacheCapacity(t *testing.T) {
+func TestParseCompactOptionsShardedDefaultsMissingPhysicalFileCacheCapacity(t *testing.T) {
shardCount := int32(4)
t.Setenv("JOB_COMPLETION_INDEX", "2")
opts := &CompactOpts{}
@@ -207,11 +207,27 @@ func TestParseCompactOptionsShardedRequiresPhysicalFileCacheCapacity(t *testing.
}
err := ParseCompactOptions(compact, opts)
- if err == nil {
- t.Fatal("expected ParseCompactOptions to fail without physicalFileCacheCapacity")
+ if err != nil {
+ t.Fatalf("expected ParseCompactOptions to succeed, got %v", err)
}
- if !strings.Contains(err.Error(), "physicalFileCacheCapacity") {
- t.Fatalf("expected physicalFileCacheCapacity validation error, got %v", err)
+ if opts.PhysicalFileCacheCapacity != "0" {
+ t.Fatalf("expected physicalFileCacheCapacity to default to %q, got %q", "0", opts.PhysicalFileCacheCapacity)
+ }
+}
+
+func TestCompactOptsVerifyAllowsZeroPhysicalFileCacheCapacity(t *testing.T) {
+ opts := CompactOpts{
+ FromTS: 1,
+ UntilTS: 2,
+ Concurrency: 1,
+ PhysicalFileCacheCapacity: "0",
+ Sharded: true,
+ ShardIndex: 0,
+ ShardCount: 3,
+ }
+
+ if err := opts.Verify(); err != nil {
+ t.Fatalf("expected Verify to allow zero physicalFileCacheCapacity, got %v", err)
}
}
@@ -226,15 +242,10 @@ func TestCompactOptsVerifyRejectsInvalidPhysicalFileCacheCapacity(t *testing.T)
capacity: "150GB",
wantErr: "invalid physicalFileCacheCapacity",
},
- {
- name: "zero quantity",
- capacity: "0",
- wantErr: "must be greater than 0",
- },
{
name: "negative quantity",
capacity: "-1G",
- wantErr: "must be greater than 0",
+ wantErr: "must be greater than or equal to 0",
},
}
diff --git a/docs/api-references/docs.md b/docs/api-references/docs.md
index 26a43bd1e4..7c309db41c 100644
--- a/docs/api-references/docs.md
+++ b/docs/api-references/docs.md
@@ -5450,7 +5450,7 @@ string
PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as –physical-file-cache-capacity. -It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and is required when mode is sharded.
+It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and defaults to “0” when omitted.PhysicalFileCacheCapacity is passed to tikv-ctl compact-log-backup as –physical-file-cache-capacity. -It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and is required when mode is sharded.
+It uses Kubernetes quantity format, e.g. “150G” or “150Gi”, and defaults to “0” when omitted.