From fa3d906d41f53a4acab41598a014add40632a5c8 Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Tue, 18 Nov 2025 11:35:17 +0100 Subject: [PATCH] Add unifiedStorageKVBackend feature toggle - Introduced the `unifiedStorageKVBackend` feature toggle to enable the use of a KV-backed SQL storage backend instead of direct SQL queries. - Updated relevant files to include the new feature toggle in the registry, CSV, JSON, and Go definitions. - Enhanced the resource server logic to conditionally use the KV backend based on the feature flag. This change expands the storage options available for the application, improving flexibility in data management. --- .../src/types/featureToggles.gen.ts | 4 ++ pkg/services/featuremgmt/registry.go | 8 +++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/featuremgmt/toggles_gen.json | 14 +++++ pkg/storage/unified/sql/server.go | 53 ++++++++++++++----- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b5602037615..0f657aadff7 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -912,6 +912,10 @@ export interface FeatureToggles { */ unifiedStorageGrpcConnectionPool?: boolean; /** + * Use KV-backed SQL storage backend instead of direct SQL queries + */ + unifiedStorageKVBackend?: boolean; + /** * Enables UI functionality to permanently delete alert rules * @default true */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 8ecc3a481e7..f6fb0591dfc 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1580,6 +1580,14 @@ var ( HideFromAdminPage: true, HideFromDocs: true, }, + { + Name: "unifiedStorageKVBackend", + Description: "Use KV-backed SQL storage backend instead of direct SQL queries", + Stage: FeatureStageExperimental, + Owner: grafanaSearchAndStorageSquad, + HideFromAdminPage: true, + HideFromDocs: true, + }, { Name: "alertingRulePermanentlyDelete", Description: "Enables UI functionality to permanently delete alert rules", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index bba9ab351d2..66c7ea7d656 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -205,6 +205,7 @@ unifiedStorageHistoryPruner,GA,@grafana/search-and-storage,false,false,false azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false localeFormatPreference,preview,@grafana/grafana-frontend-platform,false,false,false unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false +unifiedStorageKVBackend,experimental,@grafana/search-and-storage,false,false,false alertingRulePermanentlyDelete,GA,@grafana/alerting-squad,false,false,true alertingRuleRecoverDeleted,GA,@grafana/alerting-squad,false,false,true multiTenantTempCredentials,experimental,@grafana/aws-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 1f5f7b42489..f87d46a3b45 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -830,6 +830,10 @@ const ( // Enables the unified storage grpc connection pool FlagUnifiedStorageGrpcConnectionPool = "unifiedStorageGrpcConnectionPool" + // FlagUnifiedStorageKVBackend + // Use KV-backed SQL storage backend instead of direct SQL queries + FlagUnifiedStorageKVBackend = "unifiedStorageKVBackend" + // FlagAlertingRulePermanentlyDelete // Enables UI functionality to permanently delete alert rules FlagAlertingRulePermanentlyDelete = "alertingRulePermanentlyDelete" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 852e0e7f3b3..81512202e1a 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -4208,6 +4208,20 @@ "expression": "true" } }, + { + "metadata": { + "name": "unifiedStorageKVBackend", + "resourceVersion": "1763461706359", + "creationTimestamp": "2025-11-18T10:28:26Z" + }, + "spec": { + "description": "Use KV-backed SQL storage backend instead of direct SQL queries", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "unifiedStorageSearch", diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 60fa9b8467a..97a82dd8dad 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -101,21 +101,46 @@ func NewResourceServer(opts ServerOptions) (resource.ResourceServer, error) { //nolint:staticcheck // not yet migrated to OpenFeature withPruner := opts.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageHistoryPruner) - backend, err := NewBackend(BackendOptions{ - DBProvider: eDB, - Tracer: opts.Tracer, - Reg: opts.Reg, - IsHA: isHA, - withPruner: withPruner, - storageMetrics: opts.StorageMetrics, - LastImportTimeMaxAge: opts.SearchOptions.MaxIndexAge, // No need to keep last_import_times older than max index age. - }) - if err != nil { - return nil, err + // Check if KV backend is enabled via feature flag + //nolint:staticcheck // not yet migrated to OpenFeature + if opts.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageKVBackend) { + // Create SQL KV instance + sqlKV, err := resource.NewSQLKV(eDB) + if err != nil { + return nil, fmt.Errorf("create SQL KV: %w", err) + } + + // Use existing KV storage backend (already implements StorageBackend interface) + kvBackend, err := resource.NewKVStorageBackend(resource.KVBackendOptions{ + KvStore: sqlKV, + WithPruner: withPruner, + Tracer: opts.Tracer, + Reg: opts.Reg, + }) + if err != nil { + return nil, fmt.Errorf("create KV backend: %w", err) + } + serverOptions.Backend = kvBackend + // Note: kvStorageBackend doesn't implement Diagnostics/Lifecycle yet + // For now, we'll leave these nil and they will be handled by the server + } else { + // Use existing SQL backend + backend, err := NewBackend(BackendOptions{ + DBProvider: eDB, + Tracer: opts.Tracer, + Reg: opts.Reg, + IsHA: isHA, + withPruner: withPruner, + storageMetrics: opts.StorageMetrics, + LastImportTimeMaxAge: opts.SearchOptions.MaxIndexAge, // No need to keep last_import_times older than max index age. + }) + if err != nil { + return nil, err + } + serverOptions.Backend = backend + serverOptions.Diagnostics = backend + serverOptions.Lifecycle = backend } - serverOptions.Backend = backend - serverOptions.Diagnostics = backend - serverOptions.Lifecycle = backend } serverOptions.Search = opts.SearchOptions