diff --git a/server/embed/config_test.go b/server/embed/config_test.go index 9a24e5eb903..76f16827b94 100644 --- a/server/embed/config_test.go +++ b/server/embed/config_test.go @@ -100,6 +100,7 @@ func TestConfigFileFeatureGates(t *testing.T) { experimentalInitialCorruptCheck string experimentalCompactHashCheckEnabled string experimentalTxnModeWriteWithSharedBuffer string + experimentalMemoryMlock string expectErr bool expectedFeatures map[featuregate.Feature]bool }{ @@ -130,6 +131,12 @@ func TestConfigFileFeatureGates(t *testing.T) { experimentalTxnModeWriteWithSharedBuffer: "false", expectErr: true, }, + { + name: "cannot set both experimental flag and feature gate flag for MemoryMlock", + serverFeatureGatesJSON: "MemoryMlock=true", + experimentalMemoryMlock: "false", + expectErr: true, + }, { name: "ok to set different experimental flag and feature gate flag", serverFeatureGatesJSON: "DistributedTracing=true", @@ -138,6 +145,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.DistributedTracing: true, features.StopGRPCServiceOnDefrag: true, features.InitialCorruptCheck: false, + features.MemoryMlock: false, }, }, { @@ -160,6 +168,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.DistributedTracing: false, features.InitialCorruptCheck: false, features.TxnModeWriteWithSharedBuffer: true, + features.MemoryMlock: false, }, }, { @@ -170,6 +179,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.DistributedTracing: false, features.InitialCorruptCheck: false, features.TxnModeWriteWithSharedBuffer: true, + features.MemoryMlock: false, }, }, { @@ -179,6 +189,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.StopGRPCServiceOnDefrag: false, features.DistributedTracing: false, features.InitialCorruptCheck: true, + features.MemoryMlock: false, }, }, { @@ -188,6 +199,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.StopGRPCServiceOnDefrag: false, features.DistributedTracing: false, features.InitialCorruptCheck: false, + features.MemoryMlock: false, }, }, { @@ -199,6 +211,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.InitialCorruptCheck: false, features.CompactHashCheck: false, features.TxnModeWriteWithSharedBuffer: true, + features.MemoryMlock: false, }, }, { @@ -210,6 +223,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.InitialCorruptCheck: false, features.CompactHashCheck: false, features.TxnModeWriteWithSharedBuffer: false, + features.MemoryMlock: false, }, }, { @@ -219,6 +233,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.StopGRPCServiceOnDefrag: true, features.DistributedTracing: false, features.InitialCorruptCheck: false, + features.MemoryMlock: false, }, }, { @@ -228,6 +243,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.StopGRPCServiceOnDefrag: false, features.DistributedTracing: false, features.InitialCorruptCheck: true, + features.MemoryMlock: false, }, }, { @@ -237,6 +253,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.StopGRPCServiceOnDefrag: false, features.DistributedTracing: false, features.InitialCorruptCheck: false, + features.MemoryMlock: false, }, }, { @@ -247,6 +264,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.DistributedTracing: false, features.InitialCorruptCheck: false, features.TxnModeWriteWithSharedBuffer: true, + features.MemoryMlock: false, }, }, { @@ -257,6 +275,7 @@ func TestConfigFileFeatureGates(t *testing.T) { features.DistributedTracing: false, features.InitialCorruptCheck: false, features.TxnModeWriteWithSharedBuffer: false, + features.MemoryMlock: false, }, }, { @@ -292,6 +311,24 @@ func TestConfigFileFeatureGates(t *testing.T) { features.CompactHashCheck: true, }, }, + { + name: "can set feature gate MemoryMlock to true from feature gate flag", + serverFeatureGatesJSON: "MemoryMlock=true", + expectedFeatures: map[featuregate.Feature]bool{ + features.StopGRPCServiceOnDefrag: false, + features.DistributedTracing: false, + features.MemoryMlock: true, + }, + }, + { + name: "can set feature gate MemoryMlock to false from feature gate flag", + serverFeatureGatesJSON: "MemoryMlock=false", + expectedFeatures: map[featuregate.Feature]bool{ + features.StopGRPCServiceOnDefrag: false, + features.DistributedTracing: false, + features.MemoryMlock: false, + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -300,6 +337,7 @@ func TestConfigFileFeatureGates(t *testing.T) { ExperimentalInitialCorruptCheck *bool `json:"experimental-initial-corrupt-check,omitempty"` ExperimentalCompactHashCheckEnabled *bool `json:"experimental-compact-hash-check-enabled,omitempty"` ExperimentalTxnModeWriteWithSharedBuffer *bool `json:"experimental-txn-mode-write-with-shared-buffer,omitempty"` + ExperimentalMemoryMlock *bool `json:"experimental-memory-mlock,omitempty"` ServerFeatureGatesJSON string `json:"feature-gates"` }{ ServerFeatureGatesJSON: tc.serverFeatureGatesJSON, @@ -337,6 +375,14 @@ func TestConfigFileFeatureGates(t *testing.T) { yc.ExperimentalCompactHashCheckEnabled = &experimentalCompactHashCheckEnabled } + if tc.experimentalMemoryMlock != "" { + experimentalMemoryMlock, err := strconv.ParseBool(tc.experimentalMemoryMlock) + if err != nil { + t.Fatal(err) + } + yc.ExperimentalMemoryMlock = &experimentalMemoryMlock + } + b, err := yaml.Marshal(&yc) if err != nil { t.Fatal(err) diff --git a/server/embed/etcd.go b/server/embed/etcd.go index a40ef662499..c038471981d 100644 --- a/server/embed/etcd.go +++ b/server/embed/etcd.go @@ -220,7 +220,6 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { DowngradeCheckTime: cfg.ExperimentalDowngradeCheckTime, WarningApplyDuration: cfg.ExperimentalWarningApplyDuration, WarningUnaryRequestDuration: cfg.WarningUnaryRequestDuration, - ExperimentalMemoryMlock: cfg.ExperimentalMemoryMlock, ExperimentalBootstrapDefragThresholdMegabytes: cfg.ExperimentalBootstrapDefragThresholdMegabytes, ExperimentalMaxLearners: cfg.ExperimentalMaxLearners, V2Deprecation: cfg.V2DeprecationEffective(), diff --git a/server/etcdmain/help.go b/server/etcdmain/help.go index aed619c548f..22802e8a55f 100644 --- a/server/etcdmain/help.go +++ b/server/etcdmain/help.go @@ -309,7 +309,7 @@ Experimental feature: Duration of time between two downgrade status checks. --experimental-enable-lease-checkpoint-persist 'false' Enable persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. Requires experimental-enable-lease-checkpoint to be enabled. - --experimental-memory-mlock + --experimental-memory-mlock. It's deprecated, and will be decommissioned in v3.7. Use '--feature-gates=MemoryMlock=true' instead. Enable to enforce etcd pages (in particular bbolt) to stay in RAM. --experimental-snapshot-catchup-entries Number of entries for a slow follower to catch up after compacting the raft storage entries. diff --git a/server/features/etcd_features.go b/server/features/etcd_features.go index 0a5d85ce28f..4783c6204ac 100644 --- a/server/features/etcd_features.go +++ b/server/features/etcd_features.go @@ -60,6 +60,11 @@ const ( // alpha: v3.6 // main PR: https://github.com/etcd-io/etcd/pull/14120 CompactHashCheck featuregate.Feature = "CompactHashCheck" + // MemoryMlock enables mlock memory to prevent etcd from being swapped to disk. + // owner: @ptabor + // alpha: v3.6 + // main PR: https://github.com/etcd-io/etcd/pull/12750 + MemoryMlock featuregate.Feature = "MemoryMlock" ) var ( @@ -69,6 +74,7 @@ var ( InitialCorruptCheck: {Default: false, PreRelease: featuregate.Alpha}, CompactHashCheck: {Default: false, PreRelease: featuregate.Alpha}, TxnModeWriteWithSharedBuffer: {Default: true, PreRelease: featuregate.Beta}, + MemoryMlock: {Default: false, PreRelease: featuregate.Alpha}, } // ExperimentalFlagToFeatureMap is the map from the cmd line flags of experimental features // to their corresponding feature gates. @@ -78,6 +84,7 @@ var ( "experimental-initial-corrupt-check": InitialCorruptCheck, "experimental-compact-hash-check-enabled": CompactHashCheck, "experimental-txn-mode-write-with-shared-buffer": TxnModeWriteWithSharedBuffer, + "experimental-memory-mlock": MemoryMlock, } ) diff --git a/server/storage/backend.go b/server/storage/backend.go index b7b0d6861ad..1fadd6b36d0 100644 --- a/server/storage/backend.go +++ b/server/storage/backend.go @@ -23,6 +23,7 @@ import ( "go.etcd.io/etcd/server/v3/config" "go.etcd.io/etcd/server/v3/etcdserver/api/snap" + "go.etcd.io/etcd/server/v3/features" "go.etcd.io/etcd/server/v3/storage/backend" "go.etcd.io/etcd/server/v3/storage/schema" "go.etcd.io/raft/v3/raftpb" @@ -50,7 +51,7 @@ func newBackend(cfg config.ServerConfig, hooks backend.Hooks) backend.Backend { // permit 10% excess over quota for disarm bcfg.MmapSize = uint64(cfg.QuotaBackendBytes + cfg.QuotaBackendBytes/10) } - bcfg.Mlock = cfg.ExperimentalMemoryMlock + bcfg.Mlock = cfg.ServerFeatureGate.Enabled(features.MemoryMlock) bcfg.Hooks = hooks return backend.New(bcfg) }