From f6d0396fd44f91c12efd4434283315cfd52aae75 Mon Sep 17 00:00:00 2001 From: nimishgj Date: Thu, 25 Jun 2026 14:42:27 +0530 Subject: [PATCH] expose Memgraph snapshot interval/retention/on-exit --- Makefile | 3 + api/v1alpha1/memgraphcluster_types.go | 17 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 17 +++++- .../memgraph.base14.io_memgraphclusters.yaml | 19 +++++++ internal/controller/statefulset.go | 16 +++++- internal/controller/statefulset_test.go | 57 +++++++++++++++++++ 6 files changed, 127 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1dde1e7..6534254 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,9 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes lint-config: golangci-lint ## Verify golangci-lint linter configuration $(GOLANGCI_LINT) config verify +.PHONY: ci +ci: manifests generate fmt vet lint test build ## Run all CI checks: codegen, format, vet, lint, test, build. + ##@ Build .PHONY: build diff --git a/api/v1alpha1/memgraphcluster_types.go b/api/v1alpha1/memgraphcluster_types.go index 7367cd3..a7c4f42 100644 --- a/api/v1alpha1/memgraphcluster_types.go +++ b/api/v1alpha1/memgraphcluster_types.go @@ -132,6 +132,23 @@ type MemgraphConfig struct { // +kubebuilder:default=100000 // +optional WALFlushEveryNTx int32 `json:"walFlushEveryNTx,omitempty"` + + // SnapshotIntervalSec sets the periodic snapshot interval in seconds + // (Memgraph --storage-snapshot-interval-sec). Set to 0 to disable periodic + // snapshots entirely. If nil, Memgraph's built-in default (300s) is used. + // Pointer so that 0 (disabled) is distinguishable from unset. + // +optional + SnapshotIntervalSec *int32 `json:"snapshotIntervalSec,omitempty"` + + // SnapshotRetentionCount sets how many periodic snapshots Memgraph keeps on + // disk (--storage-snapshot-retention-count). If nil, Memgraph's default is used. + // +optional + SnapshotRetentionCount *int32 `json:"snapshotRetentionCount,omitempty"` + + // SnapshotOnExit controls whether Memgraph takes a snapshot on shutdown + // (--storage-snapshot-on-exit). If nil, Memgraph's default is used. + // +optional + SnapshotOnExit *bool `json:"snapshotOnExit,omitempty"` } // ReplicationSpec defines the replication settings diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b7889fd..4d5a0e5 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -110,7 +110,7 @@ func (in *MemgraphClusterSpec) DeepCopyInto(out *MemgraphClusterSpec) { *out = *in in.Resources.DeepCopyInto(&out.Resources) in.Storage.DeepCopyInto(&out.Storage) - out.Config = in.Config + in.Config.DeepCopyInto(&out.Config) in.Replication.DeepCopyInto(&out.Replication) if in.HighAvailability != nil { in, out := &in.HighAvailability, &out.HighAvailability @@ -197,6 +197,21 @@ func (in *MemgraphClusterStatus) DeepCopy() *MemgraphClusterStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MemgraphConfig) DeepCopyInto(out *MemgraphConfig) { *out = *in + if in.SnapshotIntervalSec != nil { + in, out := &in.SnapshotIntervalSec, &out.SnapshotIntervalSec + *out = new(int32) + **out = **in + } + if in.SnapshotRetentionCount != nil { + in, out := &in.SnapshotRetentionCount, &out.SnapshotRetentionCount + *out = new(int32) + **out = **in + } + if in.SnapshotOnExit != nil { + in, out := &in.SnapshotOnExit, &out.SnapshotOnExit + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemgraphConfig. diff --git a/config/crd/bases/memgraph.base14.io_memgraphclusters.yaml b/config/crd/bases/memgraph.base14.io_memgraphclusters.yaml index 4cd74d1..917ceca 100644 --- a/config/crd/bases/memgraph.base14.io_memgraphclusters.yaml +++ b/config/crd/bases/memgraph.base14.io_memgraphclusters.yaml @@ -985,6 +985,25 @@ spec: description: MemoryLimit sets the memory limit in MB for Memgraph format: int32 type: integer + snapshotIntervalSec: + description: |- + SnapshotIntervalSec sets the periodic snapshot interval in seconds + (Memgraph --storage-snapshot-interval-sec). Set to 0 to disable periodic + snapshots entirely. If nil, Memgraph's built-in default (300s) is used. + Pointer so that 0 (disabled) is distinguishable from unset. + format: int32 + type: integer + snapshotOnExit: + description: |- + SnapshotOnExit controls whether Memgraph takes a snapshot on shutdown + (--storage-snapshot-on-exit). If nil, Memgraph's default is used. + type: boolean + snapshotRetentionCount: + description: |- + SnapshotRetentionCount sets how many periodic snapshots Memgraph keeps on + disk (--storage-snapshot-retention-count). If nil, Memgraph's default is used. + format: int32 + type: integer walFlushEveryNTx: default: 100000 description: WALFlushEveryNTx sets the number of transactions diff --git a/internal/controller/statefulset.go b/internal/controller/statefulset.go index 80c730d..8f85ec2 100644 --- a/internal/controller/statefulset.go +++ b/internal/controller/statefulset.go @@ -190,12 +190,26 @@ func buildMemgraphArgs(cluster *memgraphv1alpha1.MemgraphCluster) []string { walFlushEveryNTx = defaultWALFlushEveryNTx } - return []string{ + args := []string{ fmt.Sprintf("--log-level=%s", logLevel), fmt.Sprintf("--log-file=%s/memgraph.log", dataVolumePath), fmt.Sprintf("--memory-limit=%d", memoryLimit), fmt.Sprintf("--storage-wal-file-flush-every-n-tx=%d", walFlushEveryNTx), } + + // Snapshot flags are only emitted when explicitly configured; otherwise + // Memgraph's built-in defaults apply (interval 300s, retention 3, on-exit true). + if v := cluster.Spec.Config.SnapshotIntervalSec; v != nil { + args = append(args, fmt.Sprintf("--storage-snapshot-interval-sec=%d", *v)) + } + if v := cluster.Spec.Config.SnapshotRetentionCount; v != nil { + args = append(args, fmt.Sprintf("--storage-snapshot-retention-count=%d", *v)) + } + if v := cluster.Spec.Config.SnapshotOnExit; v != nil { + args = append(args, fmt.Sprintf("--storage-snapshot-on-exit=%t", *v)) + } + + return args } // buildInitContainers builds the init containers for the pod diff --git a/internal/controller/statefulset_test.go b/internal/controller/statefulset_test.go index 527bbad..f51b987 100644 --- a/internal/controller/statefulset_test.go +++ b/internal/controller/statefulset_test.go @@ -274,6 +274,63 @@ func TestBuildMemgraphArgs(t *testing.T) { } } +func TestBuildMemgraphArgsSnapshotFlags(t *testing.T) { + i32 := func(v int32) *int32 { return &v } + b := func(v bool) *bool { return &v } + + hasArg := func(args []string, want string) bool { + for _, a := range args { + if a == want { + return true + } + } + return false + } + + t.Run("no snapshot config emits no snapshot flags", func(t *testing.T) { + args := buildMemgraphArgs(&memgraphv1alpha1.MemgraphCluster{ + Spec: memgraphv1alpha1.MemgraphClusterSpec{}, + }) + for _, a := range args { + if len(a) >= 18 && a[:18] == "--storage-snapshot" { + t.Errorf("unexpected snapshot flag %q; defaults should be left to Memgraph", a) + } + } + }) + + t.Run("interval 0 disables periodic snapshots", func(t *testing.T) { + args := buildMemgraphArgs(&memgraphv1alpha1.MemgraphCluster{ + Spec: memgraphv1alpha1.MemgraphClusterSpec{ + Config: memgraphv1alpha1.MemgraphConfig{SnapshotIntervalSec: i32(0)}, + }, + }) + if !hasArg(args, "--storage-snapshot-interval-sec=0") { + t.Errorf("expected --storage-snapshot-interval-sec=0, got args: %v", args) + } + }) + + t.Run("all snapshot flags emitted when set", func(t *testing.T) { + args := buildMemgraphArgs(&memgraphv1alpha1.MemgraphCluster{ + Spec: memgraphv1alpha1.MemgraphClusterSpec{ + Config: memgraphv1alpha1.MemgraphConfig{ + SnapshotIntervalSec: i32(3600), + SnapshotRetentionCount: i32(2), + SnapshotOnExit: b(false), + }, + }, + }) + for _, want := range []string{ + "--storage-snapshot-interval-sec=3600", + "--storage-snapshot-retention-count=2", + "--storage-snapshot-on-exit=false", + } { + if !hasArg(args, want) { + t.Errorf("expected %q, got args: %v", want, args) + } + } + }) +} + func TestBuildInitContainers(t *testing.T) { initContainers := buildInitContainers()