Skip to content

Commit fc8fa31

Browse files
authored
Add new metrics to aid troubleshooting tombstone convergence. (#4231)
* Add new metrics to aid troubleshooting tombstone convergence. - `memberlist_client_kv_store_value_tombstones` Expose the number of tombstones currently in the value for each key in the store. If the tombstones are being propagated as expected, then each instance should have the same value for this metric. - `memberlist_client_kv_store_value_tombstones_removed_total` Count the number of tombstones which have been removed because they aged out. This can be used to troubleshoot whether tombstones are being removed too soon (i.e. before the state is converged). - `memberlist_client_messages_to_broadcast_dropped_total` Count occurrences of messages being quietly dropped when they were expecting to have been broadcast. As log messages already existed for these events, the logs have had the `key` field added instead of exposing per-key metrics, for consistency with other metrics of this nature in the package. Signed-off-by: Steve Simpson <[email protected]> * Review comments. Signed-off-by: Steve Simpson <[email protected]>
1 parent ddcaac2 commit fc8fa31

File tree

7 files changed

+67
-16
lines changed

7 files changed

+67
-16
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
* [ENHANCEMENT] Added zone-awareness support to alertmanager for use when sharding is enabled. When zone-awareness is enabled, alerts will be replicated across availability zones. #4204
3434
* [ENHANCEMENT] Added `tenant_ids` tag to tracing spans #4147
3535
* [ENHANCEMENT] Ring, query-frontend: Avoid using automatic private IPs (APIPA) when discovering IP address from the interface during the registration of the instance in the ring, or by query-frontend when used with query-scheduler. APIPA still used as last resort with logging indicating usage. #4032
36+
* [ENHANCEMENT] Memberlist: introduced new metrics to aid troubleshooting tombstone convergence: #4231
37+
* `memberlist_client_kv_store_value_tombstones`
38+
* `memberlist_client_kv_store_value_tombstones_removed_total`
39+
* `memberlist_client_messages_to_broadcast_dropped_total`
3640
* [BUGFIX] Purger: fix `Invalid null value in condition for column range` caused by `nil` value in range for WriteBatch query. #4128
3741
* [BUGFIX] Ingester: fixed infrequent panic caused by a race condition between TSDB mmap-ed head chunks truncation and queries. #4176
3842
* [BUGFIX] Alertmanager: fix Alertmanager status page if clustering via gossip is disabled or sharding is enabled. #4184

integration/integration_memberlist_single_binary_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,23 @@ func testSingleBinaryEnv(t *testing.T, tlsEnabled bool) {
8686
require.NoError(t, cortex2.WaitSumMetrics(e2e.Equals(3*512), "cortex_ring_tokens_total"))
8787
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(3*512), "cortex_ring_tokens_total"))
8888

89+
// All Cortex servers should initially have no tombstones; nobody has left yet.
90+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(0), "memberlist_client_kv_store_value_tombstones"))
91+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(0), "memberlist_client_kv_store_value_tombstones"))
92+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(0), "memberlist_client_kv_store_value_tombstones"))
93+
8994
require.NoError(t, s.Stop(cortex1))
9095
require.NoError(t, cortex2.WaitSumMetrics(e2e.Equals(2*512), "cortex_ring_tokens_total"))
9196
require.NoError(t, cortex2.WaitSumMetrics(e2e.Equals(2), "memberlist_client_cluster_members_count"))
97+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(1), "memberlist_client_kv_store_value_tombstones"))
9298
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(2*512), "cortex_ring_tokens_total"))
9399
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(2), "memberlist_client_cluster_members_count"))
100+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(1), "memberlist_client_kv_store_value_tombstones"))
94101

95102
require.NoError(t, s.Stop(cortex2))
96103
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(1*512), "cortex_ring_tokens_total"))
97104
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(1), "memberlist_client_cluster_members_count"))
105+
require.NoError(t, cortex3.WaitSumMetrics(e2e.Equals(2), "memberlist_client_kv_store_value_tombstones"))
98106

99107
require.NoError(t, s.Stop(cortex3))
100108
}

pkg/ring/kv/memberlist/memberlist_client.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,16 @@ type KV struct {
252252
totalSizeOfPushes prometheus.Counter
253253
numberOfBroadcastMessagesInQueue prometheus.GaugeFunc
254254
totalSizeOfBroadcastMessagesInQueue prometheus.Gauge
255+
numberOfBroadcastMessagesDropped prometheus.Counter
255256
casAttempts prometheus.Counter
256257
casFailures prometheus.Counter
257258
casSuccesses prometheus.Counter
258259
watchPrefixDroppedNotifications *prometheus.CounterVec
259260

260-
storeValuesDesc *prometheus.Desc
261-
storeSizesDesc *prometheus.Desc
261+
storeValuesDesc *prometheus.Desc
262+
storeSizesDesc *prometheus.Desc
263+
storeTombstones *prometheus.GaugeVec
264+
storeRemovedTombstones *prometheus.CounterVec
262265

263266
memberlistMembersCount prometheus.GaugeFunc
264267
memberlistHealthScore prometheus.GaugeFunc
@@ -625,7 +628,7 @@ func (m *KV) get(key string, codec codec.Codec) (out interface{}, version uint,
625628
if mr, ok := out.(Mergeable); ok {
626629
// remove ALL tombstones before returning to client.
627630
// No need for clients to see them.
628-
mr.RemoveTombstones(time.Time{})
631+
_, _ = mr.RemoveTombstones(time.Time{})
629632
}
630633
}
631634

@@ -883,14 +886,16 @@ func (m *KV) trySingleCas(key string, codec codec.Codec, f func(in interface{})
883886
func (m *KV) broadcastNewValue(key string, change Mergeable, version uint, codec codec.Codec) {
884887
data, err := codec.Encode(change)
885888
if err != nil {
886-
level.Error(m.logger).Log("msg", "failed to encode change", "err", err)
889+
level.Error(m.logger).Log("msg", "failed to encode change", "key", key, "version", version, "err", err)
890+
m.numberOfBroadcastMessagesDropped.Inc()
887891
return
888892
}
889893

890894
kvPair := KeyValuePair{Key: key, Value: data, Codec: codec.CodecID()}
891895
pairData, err := kvPair.Marshal()
892896
if err != nil {
893-
level.Error(m.logger).Log("msg", "failed to serialize KV pair", "err", err)
897+
level.Error(m.logger).Log("msg", "failed to serialize KV pair", "key", key, "version", version, "err", err)
898+
m.numberOfBroadcastMessagesDropped.Inc()
894899
return
895900
}
896901

@@ -901,7 +906,8 @@ func (m *KV) broadcastNewValue(key string, change Mergeable, version uint, codec
901906
//
902907
// Typically messages are smaller (when dealing with couple of updates only), but can get bigger
903908
// when broadcasting result of push/pull update.
904-
level.Debug(m.logger).Log("msg", "broadcast message too big, not broadcasting", "len", len(pairData))
909+
level.Debug(m.logger).Log("msg", "broadcast message too big, not broadcasting", "key", key, "version", version, "len", len(pairData))
910+
m.numberOfBroadcastMessagesDropped.Inc()
905911
return
906912
}
907913

@@ -1191,7 +1197,9 @@ func (m *KV) mergeValueForKey(key string, incomingValue Mergeable, casVersion ui
11911197

11921198
if m.cfg.LeftIngestersTimeout > 0 {
11931199
limit := time.Now().Add(-m.cfg.LeftIngestersTimeout)
1194-
result.RemoveTombstones(limit)
1200+
total, removed := result.RemoveTombstones(limit)
1201+
m.storeTombstones.WithLabelValues(key).Set(float64(total))
1202+
m.storeRemovedTombstones.WithLabelValues(key).Add(float64(removed))
11951203
}
11961204

11971205
encoded, err := codec.Encode(result)

pkg/ring/kv/memberlist/memberlist_client_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ func (d *data) MergeContent() []string {
8383
return out
8484
}
8585

86-
func (d *data) RemoveTombstones(limit time.Time) {
86+
func (d *data) RemoveTombstones(limit time.Time) (_, _ int) {
8787
// nothing to do
88+
return
8889
}
8990

9091
func (d *data) getAllTokens() []uint32 {
@@ -870,8 +871,9 @@ func (dc distributedCounter) MergeContent() []string {
870871
return out
871872
}
872873

873-
func (dc distributedCounter) RemoveTombstones(limit time.Time) {
874+
func (dc distributedCounter) RemoveTombstones(limit time.Time) (_, _ int) {
874875
// nothing to do
876+
return
875877
}
876878

877879
type distributedCounterCodec struct{}

pkg/ring/kv/memberlist/mergeable.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ type Mergeable interface {
3838
// Remove tombstones older than given limit from this mergeable.
3939
// If limit is zero time, remove all tombstones. Memberlist client calls this method with zero limit each
4040
// time when client is accessing value from the store. It can be used to hide tombstones from the clients.
41-
RemoveTombstones(limit time.Time)
41+
// Returns the total number of tombstones present and the number of removed tombstones by this invocation.
42+
RemoveTombstones(limit time.Time) (total, removed int)
4243
}

pkg/ring/kv/memberlist/metrics.go

+24
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ func (m *KV) createAndRegisterMetrics() {
8484
Help: "Total size of messages waiting in the broadcast queue",
8585
})
8686

87+
m.numberOfBroadcastMessagesDropped = prometheus.NewCounter(prometheus.CounterOpts{
88+
Namespace: m.cfg.MetricsNamespace,
89+
Subsystem: subsystem,
90+
Name: "messages_to_broadcast_dropped_total",
91+
Help: "Number of broadcast messages intended to be sent but were dropped due to encoding errors or for being too big",
92+
})
93+
8794
m.casAttempts = prometheus.NewCounter(prometheus.CounterOpts{
8895
Namespace: m.cfg.MetricsNamespace,
8996
Subsystem: subsystem,
@@ -115,6 +122,20 @@ func (m *KV) createAndRegisterMetrics() {
115122
"Sizes of values in KV Store in bytes",
116123
[]string{"key"}, nil)
117124

125+
m.storeTombstones = prometheus.NewGaugeVec(prometheus.GaugeOpts{
126+
Namespace: m.cfg.MetricsNamespace,
127+
Subsystem: subsystem,
128+
Name: "kv_store_value_tombstones",
129+
Help: "Number of tombstones currently present in KV store values",
130+
}, []string{"key"})
131+
132+
m.storeRemovedTombstones = prometheus.NewCounterVec(prometheus.CounterOpts{
133+
Namespace: m.cfg.MetricsNamespace,
134+
Subsystem: subsystem,
135+
Name: "kv_store_value_tombstones_removed_total",
136+
Help: "Total number of tombstones which have been removed from KV store values",
137+
}, []string{"key"})
138+
118139
m.memberlistMembersCount = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
119140
Namespace: m.cfg.MetricsNamespace,
120141
Subsystem: subsystem,
@@ -162,10 +183,13 @@ func (m *KV) createAndRegisterMetrics() {
162183
m.totalSizeOfPushes,
163184
m.totalSizeOfPulls,
164185
m.totalSizeOfBroadcastMessagesInQueue,
186+
m.numberOfBroadcastMessagesDropped,
165187
m.casAttempts,
166188
m.casFailures,
167189
m.casSuccesses,
168190
m.watchPrefixDroppedNotifications,
191+
m.storeTombstones,
192+
m.storeRemovedTombstones,
169193
m.memberlistMembersCount,
170194
m.memberlistHealthScore,
171195
}

pkg/ring/model.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,19 @@ func resolveConflicts(normalizedIngesters map[string]InstanceDesc) {
374374
}
375375

376376
// RemoveTombstones removes LEFT ingesters older than given time limit. If time limit is zero, remove all LEFT ingesters.
377-
func (d *Desc) RemoveTombstones(limit time.Time) {
378-
removed := 0
377+
func (d *Desc) RemoveTombstones(limit time.Time) (total, removed int) {
379378
for n, ing := range d.Ingesters {
380-
if ing.State == LEFT && (limit.IsZero() || time.Unix(ing.Timestamp, 0).Before(limit)) {
381-
// remove it
382-
delete(d.Ingesters, n)
383-
removed++
379+
if ing.State == LEFT {
380+
if limit.IsZero() || time.Unix(ing.Timestamp, 0).Before(limit) {
381+
// remove it
382+
delete(d.Ingesters, n)
383+
removed++
384+
} else {
385+
total++
386+
}
384387
}
385388
}
389+
return
386390
}
387391

388392
func (d *Desc) getTokensInfo() map[uint32]instanceInfo {

0 commit comments

Comments
 (0)