diff --git a/internal/datastore/postgres/options.go b/internal/datastore/postgres/options.go index 709bc332fc..0635fe1345 100644 --- a/internal/datastore/postgres/options.go +++ b/internal/datastore/postgres/options.go @@ -78,7 +78,7 @@ const ( defaultExpirationDisabled = false // no follower delay by default, it should only be set if using read replicas defaultFollowerReadDelay = 0 - defaultRevisionHeartbeat = false + defaultRevisionHeartbeat = true ) // Option provides the facility to configure how clients within the diff --git a/internal/datastore/postgres/postgres.go b/internal/datastore/postgres/postgres.go index 150bc90096..865db93d41 100644 --- a/internal/datastore/postgres/postgres.go +++ b/internal/datastore/postgres/postgres.go @@ -312,14 +312,17 @@ func newPostgresDatastore( followerReadDelayNanos, ) - revisionHeartbeatQuery := fmt.Sprintf( - insertHeartBeatRevision, - colXID, - tableTransaction, - colTimestamp, - quantizationPeriodNanos, - colSnapshot, - ) + var revisionHeartbeatQuery string + if config.revisionHeartbeatEnabled { + revisionHeartbeatQuery = fmt.Sprintf( + insertHeartBeatRevision, + colXID, + tableTransaction, + colTimestamp, + quantizationPeriodNanos, + colSnapshot, + ) + } validTransactionQuery := fmt.Sprintf( queryValidTransaction, @@ -732,6 +735,11 @@ func (pgd *pgDatastore) Features(ctx context.Context) (*datastore.Features, erro } func (pgd *pgDatastore) OfflineFeatures() (*datastore.Features, error) { + continuousCheckpointing := datastore.FeatureUnsupported + if pgd.revisionHeartbeatQuery != "" { + continuousCheckpointing = datastore.FeatureSupported + } + if pgd.watchEnabled { return &datastore.Features{ Watch: datastore.Feature{ @@ -741,7 +749,7 @@ func (pgd *pgDatastore) OfflineFeatures() (*datastore.Features, error) { Status: datastore.FeatureUnsupported, }, ContinuousCheckpointing: datastore.Feature{ - Status: datastore.FeatureSupported, + Status: continuousCheckpointing, }, WatchEmitsImmediately: datastore.Feature{ Status: datastore.FeatureUnsupported, @@ -765,7 +773,7 @@ func (pgd *pgDatastore) OfflineFeatures() (*datastore.Features, error) { const defaultMaxHeartbeatLeaderJitterPercent = 10 func (pgd *pgDatastore) startRevisionHeartbeat(ctx context.Context) error { - heartbeatDuration := time.Nanosecond * time.Duration(pgd.quantizationPeriodNanos) + heartbeatDuration := max(time.Second, time.Nanosecond*time.Duration(pgd.quantizationPeriodNanos)) log.Info().Stringer("interval", heartbeatDuration).Msg("starting revision heartbeat") tick := time.NewTicker(heartbeatDuration) diff --git a/internal/datastore/postgres/postgres_shared_test.go b/internal/datastore/postgres/postgres_shared_test.go index cf901107a6..9855138035 100644 --- a/internal/datastore/postgres/postgres_shared_test.go +++ b/internal/datastore/postgres/postgres_shared_test.go @@ -173,6 +173,7 @@ func testPostgresDatastore(t *testing.T, config postgresTestConfig) { GCInterval(veryLargeGCInterval), WatchBufferLength(50), MigrationPhase(config.migrationPhase), + WithRevisionHeartbeat(false), )) t.Run("OverlappingRevisionWatch", createDatastoreTest( diff --git a/internal/datastore/postgres/postgres_test.go b/internal/datastore/postgres/postgres_test.go index 3504068179..b9a12c43b7 100644 --- a/internal/datastore/postgres/postgres_test.go +++ b/internal/datastore/postgres/postgres_test.go @@ -75,6 +75,7 @@ func TestPostgresDatastoreGC(t *testing.T) { GCInterval(veryLargeGCInterval), WatchBufferLength(1), MigrationPhase(config.migrationPhase), + WithRevisionHeartbeat(false), )) }) } diff --git a/pkg/cmd/datastore/datastore.go b/pkg/cmd/datastore/datastore.go index f8feb30c4c..256c8855e3 100644 --- a/pkg/cmd/datastore/datastore.go +++ b/pkg/cmd/datastore/datastore.go @@ -174,7 +174,7 @@ type Config struct { // Expermimental ExperimentalColumnOptimization bool `debugmap:"visible"` EnableExperimentalRelationshipExpiration bool `debugmap:"visible"` - EnableExperimentalRevisionHeartbeat bool `debugmap:"visible"` + EnableRevisionHeartbeat bool `debugmap:"visible"` } //go:generate go run github.com/ecordell/optgen -sensitive-field-name-matches uri,secure -output zz_generated.relintegritykey.options.go . RelIntegrityKey @@ -628,7 +628,7 @@ func newPostgresPrimaryDatastore(ctx context.Context, opts Config) (datastore.Da postgres.WatchBufferWriteTimeout(opts.WatchBufferWriteTimeout), postgres.MigrationPhase(opts.MigrationPhase), postgres.AllowedMigrations(opts.AllowedMigrations), - postgres.WithRevisionHeartbeat(opts.EnableExperimentalRevisionHeartbeat), + postgres.WithRevisionHeartbeat(opts.EnableRevisionHeartbeat), } commonOptions, err := commonPostgresDatastoreOptions(opts) diff --git a/pkg/cmd/datastore/zz_generated.options.go b/pkg/cmd/datastore/zz_generated.options.go index 4d50e9c749..7fe1beab8d 100644 --- a/pkg/cmd/datastore/zz_generated.options.go +++ b/pkg/cmd/datastore/zz_generated.options.go @@ -81,7 +81,7 @@ func (c *Config) ToOption() ConfigOption { to.AllowedMigrations = c.AllowedMigrations to.ExperimentalColumnOptimization = c.ExperimentalColumnOptimization to.EnableExperimentalRelationshipExpiration = c.EnableExperimentalRelationshipExpiration - to.EnableExperimentalRevisionHeartbeat = c.EnableExperimentalRevisionHeartbeat + to.EnableRevisionHeartbeat = c.EnableRevisionHeartbeat } } @@ -137,7 +137,7 @@ func (c Config) DebugMap() map[string]any { debugMap["AllowedMigrations"] = helpers.DebugValue(c.AllowedMigrations, false) debugMap["ExperimentalColumnOptimization"] = helpers.DebugValue(c.ExperimentalColumnOptimization, false) debugMap["EnableExperimentalRelationshipExpiration"] = helpers.DebugValue(c.EnableExperimentalRelationshipExpiration, false) - debugMap["EnableExperimentalRevisionHeartbeat"] = helpers.DebugValue(c.EnableExperimentalRevisionHeartbeat, false) + debugMap["EnableRevisionHeartbeat"] = helpers.DebugValue(c.EnableRevisionHeartbeat, false) return debugMap } @@ -549,9 +549,9 @@ func WithEnableExperimentalRelationshipExpiration(enableExperimentalRelationship } } -// WithEnableExperimentalRevisionHeartbeat returns an option that can set EnableExperimentalRevisionHeartbeat on a Config -func WithEnableExperimentalRevisionHeartbeat(enableExperimentalRevisionHeartbeat bool) ConfigOption { +// WithEnableRevisionHeartbeat returns an option that can set EnableRevisionHeartbeat on a Config +func WithEnableRevisionHeartbeat(enableRevisionHeartbeat bool) ConfigOption { return func(c *Config) { - c.EnableExperimentalRevisionHeartbeat = enableExperimentalRevisionHeartbeat + c.EnableRevisionHeartbeat = enableRevisionHeartbeat } } diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index d900686e37..aebf151a24 100644 --- a/pkg/cmd/serve.go +++ b/pkg/cmd/serve.go @@ -112,6 +112,7 @@ func RegisterServeFlags(cmd *cobra.Command, config *server.Config) error { apiFlags.Uint32Var(&config.MaxDeleteRelationshipsLimit, "max-delete-relationships-limit", 1000, "maximum number of relationships that can be deleted in a single request") apiFlags.Uint32Var(&config.MaxLookupResourcesLimit, "max-lookup-resources-limit", 1000, "maximum number of resources that can be looked up in a single request") apiFlags.Uint32Var(&config.MaxBulkExportRelationshipsLimit, "max-bulk-export-relationships-limit", 10_000, "maximum number of relationships that can be exported in a single request") + apiFlags.BoolVar(&config.EnableRevisionHeartbeat, "enable-revision-heartbeat", true, "enables support for revision heartbeat, used to create a synthetic revision on an interval defined by the quantization window (postgres only)") datastoreFlags := nfs.FlagSet(BoldBlue("Datastore")) // Flags for the datastore @@ -169,7 +170,6 @@ func RegisterServeFlags(cmd *cobra.Command, config *server.Config) error { Msg("The old implementation of LookupResources is no longer available, and a `false` value is no longer valid. Please remove this flag.") } - experimentalFlags.BoolVar(&config.EnableExperimentalRevisionHeartbeat, "enable-experimental-revision-heartbeat", false, "enables experimental support for postgres revision heartbeat, used to create a synthetic revision on a given interval (postgres only)") experimentalFlags.BoolVar(&config.EnableExperimentalRelationshipExpiration, "enable-experimental-relationship-expiration", false, "enables experimental support for first-class relationship expiration") experimentalFlags.BoolVar(&config.EnableExperimentalWatchableSchemaCache, "enable-experimental-watchable-schema-cache", false, "enables the experimental schema cache which makes use of the Watch API for automatic updates") // TODO: these two could reasonably be put in either the Dispatch group or the Experimental group. Is there a preference? diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 5d0e2bf109..0389a23039 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -121,7 +121,7 @@ type Config struct { MaxBulkExportRelationshipsLimit uint32 `debugmap:"visible"` EnableExperimentalLookupResources bool `debugmap:"visible"` EnableExperimentalRelationshipExpiration bool `debugmap:"visible"` - EnableExperimentalRevisionHeartbeat bool `debugmap:"visible"` + EnableRevisionHeartbeat bool `debugmap:"visible"` // Additional Services MetricsAPI util.HTTPServerConfig `debugmap:"visible"` @@ -229,7 +229,7 @@ func (c *Config) Complete(ctx context.Context) (RunnableServer, error) { // are at most the number of elements returned from a datastore query datastorecfg.WithFilterMaximumIDCount(c.DispatchChunkSize), datastorecfg.WithEnableExperimentalRelationshipExpiration(c.EnableExperimentalRelationshipExpiration), - datastorecfg.WithEnableExperimentalRevisionHeartbeat(c.EnableExperimentalRevisionHeartbeat), + datastorecfg.WithEnableRevisionHeartbeat(c.EnableRevisionHeartbeat), ) if err != nil { return nil, spiceerrors.NewTerminationErrorBuilder(fmt.Errorf("failed to create datastore: %w", err)). diff --git a/pkg/cmd/server/zz_generated.options.go b/pkg/cmd/server/zz_generated.options.go index ae035d786a..c1248b3aeb 100644 --- a/pkg/cmd/server/zz_generated.options.go +++ b/pkg/cmd/server/zz_generated.options.go @@ -89,7 +89,7 @@ func (c *Config) ToOption() ConfigOption { to.MaxBulkExportRelationshipsLimit = c.MaxBulkExportRelationshipsLimit to.EnableExperimentalLookupResources = c.EnableExperimentalLookupResources to.EnableExperimentalRelationshipExpiration = c.EnableExperimentalRelationshipExpiration - to.EnableExperimentalRevisionHeartbeat = c.EnableExperimentalRevisionHeartbeat + to.EnableRevisionHeartbeat = c.EnableRevisionHeartbeat to.MetricsAPI = c.MetricsAPI to.UnaryMiddlewareModification = c.UnaryMiddlewareModification to.StreamingMiddlewareModification = c.StreamingMiddlewareModification @@ -159,7 +159,7 @@ func (c Config) DebugMap() map[string]any { debugMap["MaxBulkExportRelationshipsLimit"] = helpers.DebugValue(c.MaxBulkExportRelationshipsLimit, false) debugMap["EnableExperimentalLookupResources"] = helpers.DebugValue(c.EnableExperimentalLookupResources, false) debugMap["EnableExperimentalRelationshipExpiration"] = helpers.DebugValue(c.EnableExperimentalRelationshipExpiration, false) - debugMap["EnableExperimentalRevisionHeartbeat"] = helpers.DebugValue(c.EnableExperimentalRevisionHeartbeat, false) + debugMap["EnableRevisionHeartbeat"] = helpers.DebugValue(c.EnableRevisionHeartbeat, false) debugMap["MetricsAPI"] = helpers.DebugValue(c.MetricsAPI, false) debugMap["SilentlyDisableTelemetry"] = helpers.DebugValue(c.SilentlyDisableTelemetry, false) debugMap["TelemetryCAOverridePath"] = helpers.DebugValue(c.TelemetryCAOverridePath, false) @@ -572,10 +572,10 @@ func WithEnableExperimentalRelationshipExpiration(enableExperimentalRelationship } } -// WithEnableExperimentalRevisionHeartbeat returns an option that can set EnableExperimentalRevisionHeartbeat on a Config -func WithEnableExperimentalRevisionHeartbeat(enableExperimentalRevisionHeartbeat bool) ConfigOption { +// WithEnableRevisionHeartbeat returns an option that can set EnableRevisionHeartbeat on a Config +func WithEnableRevisionHeartbeat(enableRevisionHeartbeat bool) ConfigOption { return func(c *Config) { - c.EnableExperimentalRevisionHeartbeat = enableExperimentalRevisionHeartbeat + c.EnableRevisionHeartbeat = enableRevisionHeartbeat } }