Files
8c825caf68 Update dskit for gRPC interface change (#5144)
* Update dskit for gRPC interface change

* Update vendor

* Update vendor

* bump dskit

* update go.sum

* manifest

---------

Co-authored-by: javiermolinar <[email protected]>
2025-05-29 16:12:21 +02:00

119 lines
3.4 KiB
Go

package ring
import (
"context"
"sync"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/grafana/dskit/kv"
"github.com/grafana/dskit/services"
)
// PartitionRingWatcher watches the partitions ring for changes in the KV store.
type PartitionRingWatcher struct {
services.Service
key string
kv kv.Client
delegate PartitionRingWatcherDelegate
logger log.Logger
ringMx sync.Mutex
ring *PartitionRing
// Metrics.
numPartitionsGaugeVec *prometheus.GaugeVec
}
type PartitionRingWatcherDelegate interface {
// OnPartitionRingChanged provides the old and new partition ring descriptions, which must not be modified.
OnPartitionRingChanged(oldRing, newRing *PartitionRingDesc)
}
func NewPartitionRingWatcher(name, key string, kv kv.Client, logger log.Logger, reg prometheus.Registerer) *PartitionRingWatcher {
r := &PartitionRingWatcher{
key: key,
kv: kv,
logger: logger,
ring: NewPartitionRing(*NewPartitionRingDesc()),
numPartitionsGaugeVec: promauto.With(reg).NewGaugeVec(prometheus.GaugeOpts{
Name: "partition_ring_partitions",
Help: "Number of partitions by state in the partitions ring.",
ConstLabels: map[string]string{"name": name},
}, []string{"state"}),
}
r.Service = services.NewBasicService(r.starting, r.loop, nil).WithName("partitions-ring-watcher")
return r
}
// WithDelegate adds the delegate to be called when the partition ring changes.
//
// Not concurrency safe.
func (w *PartitionRingWatcher) WithDelegate(delegate PartitionRingWatcherDelegate) *PartitionRingWatcher {
w.delegate = delegate
return w
}
func (w *PartitionRingWatcher) starting(ctx context.Context) error {
// Get the initial ring state so that, as soon as the service will be running, the in-memory
// ring would be already populated and there's no race condition between when the service is
// running and the WatchKey() callback is called for the first time.
value, err := w.kv.Get(ctx, w.key)
if err != nil {
return errors.Wrap(err, "unable to initialise ring state")
}
if value == nil {
level.Info(w.logger).Log("msg", "partition ring doesn't exist in KV store yet")
value = NewPartitionRingDesc()
}
w.updatePartitionRing(value.(*PartitionRingDesc))
return nil
}
func (w *PartitionRingWatcher) loop(ctx context.Context) error {
w.kv.WatchKey(ctx, w.key, func(value interface{}) bool {
if value == nil {
level.Info(w.logger).Log("msg", "partition ring doesn't exist in KV store yet")
return true
}
w.updatePartitionRing(value.(*PartitionRingDesc))
return true
})
return nil
}
func (w *PartitionRingWatcher) updatePartitionRing(desc *PartitionRingDesc) {
newRing := NewPartitionRing(*desc)
w.ringMx.Lock()
oldRing := w.ring
w.ring = newRing
w.ringMx.Unlock()
if w.delegate != nil {
w.delegate.OnPartitionRingChanged(&oldRing.desc, desc)
}
// Update metrics.
for state, count := range desc.countPartitionsByState() {
w.numPartitionsGaugeVec.WithLabelValues(state.CleanName()).Set(float64(count))
}
}
// PartitionRing returns the most updated snapshot of the PartitionRing. The returned instance
// is immutable and will not be updated if new changes are done to the ring.
func (w *PartitionRingWatcher) PartitionRing() *PartitionRing {
w.ringMx.Lock()
defer w.ringMx.Unlock()
return w.ring
}