-
Notifications
You must be signed in to change notification settings - Fork 777
keyspace: add optional keyspace info metric #11176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
77fa433
a5ebd3e
8afd0b3
fd066d1
80a0a1e
c02f996
2c24a4c
0db1607
247ace8
b0dc9cc
0ad0ff7
5f76688
c146ae5
f23de01
873377b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,7 @@ const ( | |
| // Config is the interface for keyspace config. | ||
| type Config interface { | ||
| GetPreAlloc() []string | ||
| IsKeyspaceLevelMetricsEnabled() bool | ||
| ToWaitRegionSplit() bool | ||
| GetWaitRegionSplitTimeout() time.Duration | ||
| GetCheckRegionSplitInterval() time.Duration | ||
|
|
@@ -242,7 +243,13 @@ func (manager *Manager) initReserveKeyspace(id uint32, name string) error { | |
|
|
||
| // UpdateConfig update keyspace manager's config. | ||
| func (manager *Manager) UpdateConfig(cfg Config) { | ||
| keyspaceInfoMetricsMu.Lock() | ||
| wasEnabled := manager.config.IsKeyspaceLevelMetricsEnabled() | ||
| manager.config = cfg | ||
| if wasEnabled && !cfg.IsKeyspaceLevelMetricsEnabled() { | ||
| resetKeyspaceInfoMetricsLocked() | ||
| } | ||
| keyspaceInfoMetricsMu.Unlock() | ||
| if manager.mgm != nil { | ||
| manager.mgm.updateGroups(cfg.GetMetaServiceGroups()) | ||
| } | ||
|
|
@@ -392,6 +399,7 @@ func (manager *Manager) CreateKeyspace(request *CreateKeyspaceRequest) (*keyspac | |
| zap.Uint32("keyspace-id", keyspace.GetId()), | ||
| zap.String("keyspace-name", keyspace.GetName()), | ||
| ) | ||
| manager.UpdateKeyspaceInfoMetrics(keyspace) | ||
| return keyspace, nil | ||
| } | ||
|
|
||
|
|
@@ -529,6 +537,7 @@ func (manager *Manager) CreateKeyspaceByID(request *CreateKeyspaceByIDRequest) ( | |
| zap.String("keyspace-name", keyspace.GetName()), | ||
| zap.Any("keyspace", keyspace), | ||
| ) | ||
| manager.UpdateKeyspaceInfoMetrics(keyspace) | ||
| return keyspace, nil | ||
| } | ||
|
|
||
|
|
@@ -770,6 +779,9 @@ func (manager *Manager) LoadKeyspace(name string) (*keyspacepb.KeyspaceMeta, err | |
| if manager.mgm != nil && meta != nil { | ||
| manager.mgm.AttachEndpoints(meta.GetConfig()) | ||
| } | ||
| if err == nil { | ||
| manager.UpdateKeyspaceInfoMetrics(meta) | ||
| } | ||
| return meta, err | ||
| } | ||
|
|
||
|
|
@@ -793,6 +805,9 @@ func (manager *Manager) LoadKeyspaceByID(spaceID uint32) (*keyspacepb.KeyspaceMe | |
| if manager.mgm != nil && meta != nil { | ||
| manager.mgm.AttachEndpoints(meta.GetConfig()) | ||
| } | ||
| if err == nil { | ||
| manager.UpdateKeyspaceInfoMetrics(meta) | ||
|
nolouch marked this conversation as resolved.
Outdated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: a load that read before deletion can permanently recreate the deleted series. The read transaction finishes before this metric update. A concurrent The metric publication needs ordering or version validation against removal. A second unsynchronized existence check would leave the same check-then-write race. Unit-test reproducerAdd type blockingKeyspaceStorage struct {
endpoint.KeyspaceStorage
blockNext atomic.Bool
reached chan struct{}
release chan struct{}
}
func (s *blockingKeyspaceStorage) RunInTxn(ctx context.Context, f func(kv.Txn) error) error {
err := s.KeyspaceStorage.RunInTxn(ctx, f)
if s.blockNext.CompareAndSwap(true, false) {
close(s.reached)
<-s.release
}
return err
}
func (suite *keyspaceTestSuite) TestStaleLoadCannotRestoreRemovedInfoMetric() {
re := suite.Require()
suite.manager.UpdateConfig(&mockConfig{EnableKeyspaceLevelMetrics: true})
resetKeyspaceInfoMetrics()
created, err := suite.manager.CreateKeyspace(&CreateKeyspaceRequest{
Name: "review_stale_load", CreateTime: time.Now().Unix(),
})
re.NoError(err)
for _, state := range []keyspacepb.KeyspaceState{
keyspacepb.KeyspaceState_DISABLED,
keyspacepb.KeyspaceState_ARCHIVED,
keyspacepb.KeyspaceState_TOMBSTONE,
} {
_, err = suite.manager.UpdateKeyspaceState(created.GetName(), state, time.Now().Unix())
re.NoError(err)
}
store := &blockingKeyspaceStorage{
KeyspaceStorage: suite.manager.store,
reached: make(chan struct{}),
release: make(chan struct{}),
}
suite.manager.store = store
store.blockNext.Store(true)
loadDone := make(chan error, 1)
go func() {
_, loadErr := suite.manager.LoadKeyspaceByID(created.GetId())
loadDone <- loadErr
}()
<-store.reached
re.NoError(suite.manager.RemoveKeyspace(created.GetId()))
re.Equal(0, promtestutil.CollectAndCount(keyspaceInfo))
close(store.release)
re.NoError(<-loadDone)
re.Equal(0, promtestutil.CollectAndCount(keyspaceInfo))
}Run: On
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This metric is now defined as a client-observed mapping, not a strongly consistent metadata snapshot. A concurrent deletion may leave a previously observed mapping temporarily, but deleted keyspaces won’t have active storage-size series to join with. Avoiding this window would require extra lifecycle synchronization, which we want to avoid here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current head makes this broader than the concurrent window described above: no successful removal path deletes an observed series at all. func (suite *keyspaceTestSuite) TestRemovalClearsObservedInfoMetric() {
re := suite.Require()
resetKeyspaceInfoMetrics()
suite.T().Cleanup(resetKeyspaceInfoMetrics)
created, err := suite.manager.CreateKeyspace(&CreateKeyspaceRequest{
Name: "review_rm_metric", CreateTime: time.Now().Unix(),
})
re.NoError(err)
SetKeyspaceInfoMetrics(created.GetId(), created.GetName())
re.Equal(1, testutil.CollectAndCount(keyspaceInfo))
for _, state := range []keyspacepb.KeyspaceState{
keyspacepb.KeyspaceState_DISABLED,
keyspacepb.KeyspaceState_ARCHIVED,
} {
_, err = suite.manager.UpdateKeyspaceStateByID(
created.GetId(), state, time.Now().Unix())
re.NoError(err)
}
re.NoError(suite.manager.store.RunInTxn(suite.ctx, func(txn kv.Txn) error {
return suite.manager.RemoveKeyspace(txn, created.GetId())
}))
re.Equal(0, testutil.CollectAndCount(keyspaceInfo))
}Run: On This is explicitly required by Issue #11175. The fact that another storage-size metric may currently have no sample does not make stale high-cardinality labels safe: ID reuse makes the stale series ambiguous, and repeated create/remove cycles retain series indefinitely. Please restore post-commit cleanup. |
||
| } | ||
| return meta, err | ||
| } | ||
|
|
||
|
|
@@ -1035,33 +1050,77 @@ func (manager *Manager) UpdateKeyspaceState(name string, newState keyspacepb.Key | |
| return meta, nil | ||
| } | ||
|
|
||
| // RemoveKeyspace removes the keyspace specified by id if it's in proper state and not protected. | ||
| func (manager *Manager) RemoveKeyspace(txn kv.Txn, id uint32) error { | ||
| // RemoveKeyspace removes the keyspace specified by id and clears its caches after the transaction commits. | ||
| func (manager *Manager) RemoveKeyspace(id uint32) error { | ||
| var removed *keyspacepb.KeyspaceMeta | ||
| err := manager.store.RunInTxn(manager.ctx, func(txn kv.Txn) error { | ||
| var err error | ||
| removed, err = manager.stageRemoveKeyspace(txn, id) | ||
| return err | ||
| }) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| manager.finishRemoveKeyspace(removed) | ||
| return nil | ||
| } | ||
|
|
||
| // stageRemoveKeyspace stages keyspace removal and returns its metadata for post-commit cleanup. | ||
| func (manager *Manager) stageRemoveKeyspace(txn kv.Txn, id uint32) (*keyspacepb.KeyspaceMeta, error) { | ||
| manager.metaLock.Lock(id) | ||
| defer manager.metaLock.Unlock(id) | ||
| if isProtectedKeyspaceID(id) { | ||
| return newModifyProtectedKeyspaceError() | ||
| return nil, newModifyProtectedKeyspaceError() | ||
| } | ||
| meta, err := manager.store.LoadKeyspaceMeta(txn, id) | ||
| if err != nil { | ||
| return err | ||
| return nil, err | ||
| } | ||
| if meta == nil { | ||
| return errs.ErrKeyspaceNotFound | ||
| return nil, errs.ErrKeyspaceNotFound | ||
| } | ||
| if meta.GetState() == keyspacepb.KeyspaceState_ENABLED || meta.GetState() == keyspacepb.KeyspaceState_DISABLED { | ||
| return errors.Errorf("cannot remove keyspace in state %s", meta.GetState().String()) | ||
| return nil, errors.Errorf("cannot remove keyspace in state %s", meta.GetState().String()) | ||
| } | ||
| err = manager.store.RemoveKeyspace(txn, id, meta.GetName()) | ||
| if err != nil { | ||
| return err | ||
| return nil, err | ||
| } | ||
| manager.keyspaceNameLookup.Delete(id) | ||
| manager.keyspaceStateLookup.Delete(id) | ||
| // Keep the meta-service group assignment accounting in sync within the same | ||
| // txn. Without this, removed keyspaces leak count and could permanently block | ||
| // deleting an otherwise-empty group. | ||
| return manager.unassignKeyspaceFromMetaServiceGroup(txn, meta) | ||
| if err := manager.unassignKeyspaceFromMetaServiceGroup(txn, meta); err != nil { | ||
| return nil, err | ||
| } | ||
| return meta, nil | ||
| } | ||
|
|
||
| // finishRemoveKeyspace clears the caches and metrics after a keyspace removal transaction commits. | ||
| func (manager *Manager) finishRemoveKeyspace(meta *keyspacepb.KeyspaceMeta) { | ||
| manager.keyspaceNameLookup.Delete(meta.GetId()) | ||
| manager.keyspaceStateLookup.Delete(meta.GetId()) | ||
| keyspaceInfoMetricsMu.Lock() | ||
| defer keyspaceInfoMetricsMu.Unlock() | ||
| if manager.config.IsKeyspaceLevelMetricsEnabled() { | ||
| deleteKeyspaceInfoMetricsLocked(meta.GetId(), meta.GetName()) | ||
| } | ||
| } | ||
|
|
||
| // UpdateKeyspaceInfoMetrics updates the keyspace ID-to-name mapping metric when keyspace-level metrics are enabled. | ||
| func (manager *Manager) UpdateKeyspaceInfoMetrics(meta *keyspacepb.KeyspaceMeta) { | ||
| keyspaceInfoMetricsMu.Lock() | ||
| defer keyspaceInfoMetricsMu.Unlock() | ||
| if meta == nil || !manager.config.IsKeyspaceLevelMetricsEnabled() { | ||
|
nolouch marked this conversation as resolved.
Outdated
|
||
| return | ||
| } | ||
| setKeyspaceInfoMetricsLocked(meta.GetId(), meta.GetName()) | ||
| } | ||
|
|
||
| // DeleteKeyspaceInfoMetrics removes the keyspace info series for id. | ||
| func (*Manager) DeleteKeyspaceInfoMetrics(id uint32) { | ||
| keyspaceInfoMetricsMu.Lock() | ||
| defer keyspaceInfoMetricsMu.Unlock() | ||
| deleteKeyspaceInfoMetricsByIDLocked(id) | ||
| } | ||
|
|
||
| // UpdateKeyspaceStateByID updates target keyspace to the given state if it's not already in that state. | ||
|
|
@@ -1190,6 +1249,9 @@ func (manager *Manager) LoadRangeKeyspace(startID uint32, limit int) ([]*keyspac | |
| } | ||
| } | ||
| } | ||
| for _, meta := range keyspaces { | ||
| manager.UpdateKeyspaceInfoMetrics(meta) | ||
| } | ||
| return keyspaces, nil | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| package keyspace | ||
|
|
||
| import ( | ||
| "strconv" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/prometheus/client_golang/prometheus" | ||
|
|
@@ -40,6 +42,15 @@ const ( | |
| ) | ||
|
|
||
| var ( | ||
| keyspaceInfo = prometheus.NewGaugeVec( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: bootstrap never publishes the reserved keyspace mapping. When Unit-test reproducerfunc (suite *keyspaceTestSuite) TestBootstrapPublishesKeyspaceInfoMetric() {
re := suite.Require()
resetKeyspaceInfoMetrics()
store := endpoint.NewStorageEndpoint(kv.NewMemoryKV(), nil)
kgm := NewKeyspaceGroupManager(suite.ctx, store, nil)
re.NoError(kgm.Bootstrap(suite.ctx))
manager := NewKeyspaceManager(
suite.ctx,
store,
nil,
mockid.NewIDAllocator(),
&mockConfig{EnableKeyspaceLevelMetrics: true},
kgm,
nil,
)
re.NoError(manager.Bootstrap())
id, name := GetBootstrapKeyspaceID(), GetBootstrapKeyspaceName()
re.Equal(1, promtestutil.CollectAndCount(keyspaceInfo))
re.Equal(float64(1), promtestutil.ToFloat64(keyspaceInfo.WithLabelValues(
strconv.FormatUint(uint64(id), 10), name)))
}Run: On
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This metric is populated on demand. CSE calls |
||
| prometheus.GaugeOpts{ | ||
| Namespace: namespace, | ||
| Subsystem: subsystem, | ||
| Name: "info", | ||
| Help: "Keyspace metadata. The value is always 1.", | ||
| }, []string{"keyspace_id", "keyspace_name"}) | ||
| keyspaceInfoMetricsMu sync.Mutex | ||
|
|
||
| createKeyspaceStepDuration = prometheus.NewHistogramVec( | ||
| prometheus.HistogramOpts{ | ||
| Namespace: namespace, | ||
|
|
@@ -60,6 +71,7 @@ var ( | |
| ) | ||
|
|
||
| func init() { | ||
| prometheus.MustRegister(keyspaceInfo) | ||
| prometheus.MustRegister(createKeyspaceStepDuration) | ||
| createKeyspaceStepDurationTotal = createKeyspaceStepDuration.WithLabelValues(StepTotal) | ||
| createKeyspaceStepDurationAllocateID = createKeyspaceStepDuration.WithLabelValues(StepAllocateID) | ||
|
|
@@ -70,6 +82,35 @@ func init() { | |
| createKeyspaceStepDurationUpdateKG = createKeyspaceStepDuration.WithLabelValues(StepUpdateKeyspaceGroup) | ||
| } | ||
|
|
||
| // setKeyspaceInfoMetricsLocked updates a keyspace info series while keyspaceInfoMetricsMu is held. | ||
| func setKeyspaceInfoMetricsLocked(id uint32, name string) { | ||
| keyspaceInfo.WithLabelValues(strconv.FormatUint(uint64(id), 10), name).Set(1) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: a former leader can export both the old and new names for one keyspace ID. These metrics are process-local, while every load/create/remove handler is leader-only. A PD can populate an ID, lose leadership, and miss the removal and With the current leader-only population model, reset these series when a PD stops serving as leader, matching the existing scheduling-metric lifecycle, and keep per-ID state so observing a different name replaces the previous exact child. Alternatively, a leader-independent watcher would have to maintain every PD's local registry. The per-ID state should retain the cached gauge and name: each client Unit-test reproducerThe direct storage transactions below model changes committed to shared etcd by another PD process. That process can clean its own Prometheus registry, but not this process's registry. func (suite *keyspaceTestSuite) TestExternalRecreationReplacesLocalInfoMetric() {
re := suite.Require()
suite.manager.UpdateConfig(&mockConfig{EnableKeyspaceLevelMetrics: true})
created, err := suite.manager.CreateKeyspace(&CreateKeyspaceRequest{
Name: "review_external", CreateTime: time.Now().Unix(),
})
re.NoError(err)
for _, state := range []keyspacepb.KeyspaceState{
keyspacepb.KeyspaceState_DISABLED,
keyspacepb.KeyspaceState_ARCHIVED,
keyspacepb.KeyspaceState_TOMBSTONE,
} {
_, err = suite.manager.UpdateKeyspaceState(created.GetName(), state, time.Now().Unix())
re.NoError(err)
}
re.NoError(suite.manager.store.RunInTxn(suite.ctx, func(txn kv.Txn) error {
return suite.manager.store.RemoveKeyspace(txn, created.GetId(), created.GetName())
}))
recreated := &keyspacepb.KeyspaceMeta{
Keyspace: &keyspacepb.KeyspaceMeta_Id{Id: created.GetId()},
Name: "review_recreated",
State: keyspacepb.KeyspaceState_ENABLED,
}
re.NoError(suite.manager.store.RunInTxn(suite.ctx, func(txn kv.Txn) error {
if err := suite.manager.store.SaveKeyspaceID(
txn, recreated.GetId(), recreated.GetName(),
); err != nil {
return err
}
return suite.manager.store.SaveKeyspaceMeta(txn, recreated)
}))
_, err = suite.manager.LoadKeyspaceByID(recreated.GetId())
re.NoError(err)
expected := fmt.Sprintf(
"# HELP pd_keyspace_info Keyspace metadata. The value is always 1.\n"+
"# TYPE pd_keyspace_info gauge\n"+
"pd_keyspace_info{keyspace_id=\"%d\",keyspace_name=\"%s\"} 1\n",
recreated.GetId(), recreated.GetName(),
)
re.NoError(promtestutil.CollectAndCompare(
keyspaceInfo, strings.NewReader(expected), "pd_keyspace_info",
))
}Run: On
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A new observation now removes any existing series for the same keyspace ID before setting the current name, so one ID won’t have two names. We intentionally allow a PD to retain its last observed mapping because this metric is client-observed rather than an authoritative snapshot.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: the current implementation can export two names for one ID. The latest commit removed the per-ID deletion and changed the test from Unit-test reproducerfunc (suite *keyspaceTestSuite) TestReusedIDReplacesObservedName() {
re := suite.Require()
resetKeyspaceInfoMetrics()
suite.T().Cleanup(resetKeyspaceInfoMetrics)
id := uint32(42)
oldMeta, err := suite.manager.CreateKeyspaceByID(&CreateKeyspaceByIDRequest{
ID: &id, Name: "review_old_name", CreateTime: time.Now().Unix(),
})
re.NoError(err)
SetKeyspaceInfoMetrics(oldMeta.GetId(), oldMeta.GetName())
for _, state := range []keyspacepb.KeyspaceState{
keyspacepb.KeyspaceState_DISABLED,
keyspacepb.KeyspaceState_ARCHIVED,
} {
_, err = suite.manager.UpdateKeyspaceStateByID(id, state, time.Now().Unix())
re.NoError(err)
}
_, err = suite.manager.kgm.RemoveKeyspacesFromGroup(
constant.DefaultKeyspaceGroupID, suite.manager, []uint32{id})
re.NoError(err)
newMeta, err := suite.manager.CreateKeyspaceByID(&CreateKeyspaceByIDRequest{
ID: &id, Name: "review_new_name", CreateTime: time.Now().Unix(),
})
re.NoError(err)
SetKeyspaceInfoMetrics(newMeta.GetId(), newMeta.GetName())
expected := strings.NewReader(
"# HELP pd_keyspace_info Keyspace ID-to-name mappings observed through client requests. The value is always 1.\n" +
"# TYPE pd_keyspace_info gauge\n" +
"pd_keyspace_info{keyspace_id=\"42\",keyspace_name=\"review_new_name\"} 1\n",
)
re.NoError(testutil.CollectAndCompare(
keyspaceInfo, expected, "pd_keyspace_info"))
}Run: On Please retain per-ID
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This regressed in the latest commit (f23de01, "avoid redundant info metric deletion"), which dropped the |
||
| } | ||
|
|
||
| // deleteKeyspaceInfoMetricsLocked deletes a keyspace info series while keyspaceInfoMetricsMu is held. | ||
| func deleteKeyspaceInfoMetricsLocked(id uint32, name string) { | ||
| keyspaceInfo.DeleteLabelValues(strconv.FormatUint(uint64(id), 10), name) | ||
| } | ||
|
|
||
| // deleteKeyspaceInfoMetricsByIDLocked deletes keyspace info series by ID while keyspaceInfoMetricsMu is held. | ||
| func deleteKeyspaceInfoMetricsByIDLocked(id uint32) { | ||
| keyspaceInfo.DeletePartialMatch(prometheus.Labels{ | ||
| "keyspace_id": strconv.FormatUint(uint64(id), 10), | ||
| }) | ||
| } | ||
|
|
||
| // resetKeyspaceInfoMetrics deletes all keyspace info series and cached gauges. | ||
| func resetKeyspaceInfoMetrics() { | ||
| keyspaceInfoMetricsMu.Lock() | ||
| defer keyspaceInfoMetricsMu.Unlock() | ||
| resetKeyspaceInfoMetricsLocked() | ||
| } | ||
|
|
||
| // resetKeyspaceInfoMetricsLocked resets the metric while keyspaceInfoMetricsMu is held. | ||
| func resetKeyspaceInfoMetricsLocked() { | ||
| keyspaceInfo.Reset() | ||
| } | ||
|
|
||
| // createKeyspaceTracer traces create-keyspace steps: one callback per step (same pattern as RegionHeartbeatProcessTracer), records metrics and logs per step. | ||
| type createKeyspaceTracer struct { | ||
| beginTime time.Time | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking: the metric is published later than the metadata it represents.
UpdateKeyspaceStateByIDhas already committed anENABLEDmetadata record beforeUpdateKeyspaceForGroupruns. If that separate group write fails, this method returns here without rolling the metadata back and without reaching the metric update below. The durable keyspace cannot be retried under the same name, yet its mapping remains absent until an unrelated load or watch happens.CreateKeyspaceByIDhas the same ordering.Please publish once the metadata becomes durable and remove the series only when a rollback deletion commits, or make every subsequent failure roll the metadata back. Whether a client has opened
WatchKeyspacesshould not change the result.Unit-test reproducer
Run:
On
247ace82be, the persisted state isENABLED, but the final assertion reportsexpected: 1, actual: 0.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This no longer depends on the create flow. The metric is now populated from successful
GetAllKeyspacesandLoadKeyspaceByIDrequests, so partial create failures won’t directly update it.