Files
pubsub/server/feature_flags.go

131 lines
4.0 KiB
Go

// Copyright 2026 The NATS Authors
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"maps"
"slices"
"strings"
)
const (
FeatureFlagJsAckFormatV2 = "js_ack_fc_v2"
FeatureFlagJsRaftDeleteRange = "js_raft_delete_range"
)
var featureFlags = map[string]bool{
// Use v2 format for `$JS.ACK.>` and `$JS.FC.>`.
// - Introduced: 2.14.0, both v1 and v2 supported, only using v1.
// - Enabled: TBD, both supported, v2 becomes the default.
//
// - v1: $JS.ACK.<stream name>.<consumer name>.<num delivered>.<stream sequence>.<consumer sequence>.<timestamp>.<num pending>
// - v2: $JS.ACK.<domain>.<account hash>.<stream name>.<consumer name>.<num delivered>.<stream sequence>.<consumer sequence>.<timestamp>.<num pending>
// See also: https://github.com/nats-io/nats-architecture-and-design/blob/main/adr/ADR-15.md#jsack
FeatureFlagJsAckFormatV2: false,
// Propose delete range gaps as a single `deleteRangeOp` Raft append entry
// instead of one entry per deleted sequence. Dramatically reduces Raft cost
// on mirrors whose origin has a large number of interior deletes.
// - Introduced: 2.14.0, apply-side always supports receiving `deleteRangeOp`.
// - Enabled: TBD, once all supported versions carry the apply-side.
//
// WARNING: Only enable once every peer in the cluster is on a version that
// supports receiving `deleteRangeOp`. Older peers panic on apply of an
// unknown stream entry operation.
FeatureFlagJsRaftDeleteRange: false,
}
// getFeatureFlag is used to retrieve either the default or overwritten value for a feature flag.
// The user's value takes precedence over the system's default. However, if the flag doesn't exist, it's disabled.
// The *Options returned by Server.getOpts() is treated as immutable, mutations go through setOpts,
// so no lock is required on the map read here.
func (o *Options) getFeatureFlag(k string) bool {
defaultValue, ok := featureFlags[k]
if !ok {
return false // Not supported.
}
if userValue, ok := o.FeatureFlags[k]; ok {
return userValue
}
return defaultValue
}
// getMergedFeatureFlags returns a merged map of feature flags, with the user's values taking precedence.
func (o *Options) getMergedFeatureFlags() map[string]bool {
merged := make(map[string]bool)
for k, v := range featureFlags {
merged[k] = v
}
for k, v := range o.FeatureFlags {
if _, ok := featureFlags[k]; !ok {
continue
}
merged[k] = v
}
return merged
}
// printFeatureFlags logs the currently used feature flags on server startup.
func (s *Server) printFeatureFlags(o *Options) {
if len(o.FeatureFlags) == 0 {
return
}
keys := slices.Sorted(maps.Keys(o.FeatureFlags))
var (
configured strings.Builder
unsupported strings.Builder
)
for _, k := range keys {
// Unsupported
defaultValue, ok := featureFlags[k]
if !ok {
if unsupported.Len() > 0 {
unsupported.WriteString(", ")
}
unsupported.WriteString(k)
continue
}
v := o.FeatureFlags[k]
if configured.Len() > 0 {
configured.WriteString(", ")
}
configured.WriteString(k)
configured.WriteString(" (")
if defaultValue {
if v {
configured.WriteString("enabled")
} else {
configured.WriteString("opt-out")
}
} else if v {
configured.WriteString("opt-in")
} else {
configured.WriteString("disabled")
}
configured.WriteString(")")
}
if configured.Len() == 0 {
configured.WriteString("none")
}
s.Noticef(" Feature flags:")
s.Noticef(" Configured: %s", configured.String())
if unsupported.Len() > 0 {
s.Noticef(" Unsupported: %s", unsupported.String())
}
}